mirror of https://github.com/periph/devices
Add TI HDC302x Sensor Support (#83)
* Fix race conditions in test SenseContinuous() Fixes #84pull/86/head
parent
7fd42d5ebf
commit
72857e8c3f
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2024 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 hdc302x_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/i2c/i2creg"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
"periph.io/x/devices/v3/hdc302x"
|
||||||
|
"periph.io/x/host/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
// Make sure periph is initialized.
|
||||||
|
if _, err := host.Init(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// Open default I²C bus.
|
||||||
|
bus, err := i2creg.Open("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to open I²C: %v", err)
|
||||||
|
}
|
||||||
|
defer bus.Close()
|
||||||
|
|
||||||
|
// Create the Sensor
|
||||||
|
sensor, err := hdc302x.NewI2C(bus, hdc302x.DefaultSensorAddress, hdc302x.RateFourHertz)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// Take a reading
|
||||||
|
env := physic.Env{}
|
||||||
|
err = sensor.Sense(&env)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Sensor Output: %s\n", env)
|
||||||
|
}
|
||||||
@ -0,0 +1,679 @@
|
|||||||
|
// Copyright 2024 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.
|
||||||
|
|
||||||
|
// This package provides a driver for the Texas Instruments HDC3021/3022
|
||||||
|
// I2C Temperature/Humidity Sensors. This is a high accuracy sensor with
|
||||||
|
// very good resolution.
|
||||||
|
//
|
||||||
|
// Datasheet
|
||||||
|
//
|
||||||
|
// https://www.ti.com/lit/ds/symlink/hdc3022.pdf
|
||||||
|
package hdc302x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3"
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SampleRate uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Constants for the sample rate to use for the measurements. The datasheet
|
||||||
|
// recommends not sampling more often than once per second to avoid self-heating
|
||||||
|
// of the sensor.
|
||||||
|
//
|
||||||
|
// Every other second
|
||||||
|
RateHalfHertz SampleRate = iota
|
||||||
|
// Sample 1x Second.
|
||||||
|
RateHertz
|
||||||
|
RateTwoHertz
|
||||||
|
RateFourHertz
|
||||||
|
Rate10Hertz
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dev represents a hdc302x sensor.
|
||||||
|
type Dev struct {
|
||||||
|
d *i2c.Dev
|
||||||
|
shutdown chan struct{}
|
||||||
|
mu sync.Mutex
|
||||||
|
sampleRate SampleRate
|
||||||
|
halted bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// The alert function works with pairs of values Temperature/Humidity. A
|
||||||
|
// Threshold is a set of humidity/temperature values defining an upper or
|
||||||
|
// lower limit for alerts.
|
||||||
|
type Threshold struct {
|
||||||
|
Humidity physic.RelativeHumidity
|
||||||
|
Temperature physic.Temperature
|
||||||
|
}
|
||||||
|
|
||||||
|
// For alert and clear, there is a pair of temperatures. For example,
|
||||||
|
// a low alert value, and a clear low alert value. There's a value
|
||||||
|
// for each measurement parameter.
|
||||||
|
type ThresholdPair struct {
|
||||||
|
Low Threshold
|
||||||
|
High Threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusWord uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Status flags returned by ReadStatus()
|
||||||
|
StatusActiveAlerts StatusWord = 1 << 15
|
||||||
|
StatusHeaterEnabled StatusWord = 1 << 13
|
||||||
|
// Mirrored on the alert pin.
|
||||||
|
StatusRHTrackingAlert StatusWord = 1 << 11
|
||||||
|
// Also reflected on alert pin
|
||||||
|
StatusTempTrackingAlert StatusWord = 1 << 10
|
||||||
|
StatusRHHighTrackingAlert StatusWord = 1 << 9
|
||||||
|
StatusRHLowTrackingAlert StatusWord = 1 << 8
|
||||||
|
StatusTempHighTrackingAlert StatusWord = 1 << 7
|
||||||
|
StatusTempLowTrackingAlert StatusWord = 1 << 6
|
||||||
|
StatusDeviceReset StatusWord = 1 << 4
|
||||||
|
// Set if there was a CRC error on the last write command.
|
||||||
|
StatusLastWriteCRCFailure StatusWord = 1 << 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Configuration provides information about the running device's config.
|
||||||
|
type Configuration struct {
|
||||||
|
// Device unique ID. Read-Only
|
||||||
|
SerialNumber int64
|
||||||
|
// Numeric vendor ID. Read-Only
|
||||||
|
VendorID uint16
|
||||||
|
// Status Word. Refer to the Status* constants above, and the datasheet for
|
||||||
|
// usage.
|
||||||
|
Status StatusWord
|
||||||
|
// refer to the Rate constants. Read-Only
|
||||||
|
SampleRate SampleRate
|
||||||
|
// Offset for RH calculation. Note that these offsets are approximate,
|
||||||
|
// so a request to set the offset to -5%rH may result in an offset of
|
||||||
|
// -4.8%rH. This is an artifact of the device's offset implementation.
|
||||||
|
// Refer to the datasheet for more information.
|
||||||
|
HumidityOffset physic.RelativeHumidity
|
||||||
|
// Offset for Temp result. Note that the data sheet states this is not
|
||||||
|
// used in the RH calculation.
|
||||||
|
TemperatureOffset physic.Temperature
|
||||||
|
|
||||||
|
// High/Low thresholds for triggering alerts. As with the offsets,
|
||||||
|
// written values are not precise.
|
||||||
|
AlertThresholds ThresholdPair
|
||||||
|
// High/Low threshold for clearing alerts.
|
||||||
|
ClearThresholds ThresholdPair
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// The default i2c bus address for this device.
|
||||||
|
DefaultSensorAddress uint16 = 0x44
|
||||||
|
)
|
||||||
|
|
||||||
|
type HeaterPower uint16
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Constants for setting the heater's power setting.
|
||||||
|
PowerFull HeaterPower = 0x3fff
|
||||||
|
PowerHalf HeaterPower = 0x03ff
|
||||||
|
PowerQuarter HeaterPower = 0x9f
|
||||||
|
PowerOff HeaterPower = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
type devCommand []byte
|
||||||
|
|
||||||
|
// Sample Rate commands
|
||||||
|
var measure2Seconds = devCommand{0x20, 0x32}
|
||||||
|
var measureSecond = devCommand{0x21, 0x30}
|
||||||
|
var measure2xSecond = devCommand{0x22, 0x36}
|
||||||
|
var measure4xSecond = devCommand{0x23, 0x34}
|
||||||
|
var measure10xSecond = devCommand{0x27, 0x37}
|
||||||
|
|
||||||
|
var sampleRateCommands = []devCommand{measure2Seconds, measureSecond, measure2xSecond, measure4xSecond, measure10xSecond}
|
||||||
|
var sampleRateDurations = []time.Duration{2 * time.Second, time.Second, 500 * time.Millisecond, 250 * time.Millisecond, 100 * time.Millisecond}
|
||||||
|
|
||||||
|
// Other device commands
|
||||||
|
var clearStatus = devCommand{0x30, 0x41}
|
||||||
|
var disableHeater = devCommand{0x30, 0x66}
|
||||||
|
var enableHeater = devCommand{0x30, 0x6d}
|
||||||
|
var read = devCommand{0xe0, 0x0}
|
||||||
|
var readSetHeater = devCommand{0x30, 0x6e}
|
||||||
|
var readSetOffsets = devCommand{0xa0, 0x04}
|
||||||
|
var readStatus = devCommand{0xf3, 0x2d}
|
||||||
|
var readVendorID = devCommand{0x37, 0x81}
|
||||||
|
var reset = devCommand{0x30, 0xa2}
|
||||||
|
var stopContinuousReadings = devCommand{0x30, 0x93}
|
||||||
|
|
||||||
|
// read/write alert threshold commands.
|
||||||
|
var readLowAlertThresholds = devCommand{0xe1, 0x02}
|
||||||
|
var readHighAlertThresholds = devCommand{0xe1, 0x1f}
|
||||||
|
var readLowClearThresholds = devCommand{0xe1, 0x09}
|
||||||
|
var readHighClearThresholds = devCommand{0xe1, 0x14}
|
||||||
|
var writeLowAlertThresholds = devCommand{0x61, 0x00}
|
||||||
|
var writeHighAlertThresholds = devCommand{0x61, 0x1d}
|
||||||
|
var writeLowClearThresholds = devCommand{0x61, 0x0b}
|
||||||
|
var writeHighClearThresholds = devCommand{0x61, 0x16}
|
||||||
|
|
||||||
|
var errInvalidCRC = errors.New("hdc302x: invalid crc")
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Magic numbers for count to value conversions.
|
||||||
|
temperatureOffset float64 = -45.0
|
||||||
|
temperatureScalar float64 = 175.0
|
||||||
|
humidityScalar float64 = 100.0
|
||||||
|
scaleDivisor float64 = 65535.0
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewI2C returns a new HDC302x sensor using the specified bus, address, and
|
||||||
|
// sample rate.
|
||||||
|
func NewI2C(b i2c.Bus, addr uint16, sampleRate SampleRate) (*Dev, error) {
|
||||||
|
dev := &Dev{d: &i2c.Dev{Bus: b, Addr: addr}, shutdown: nil, sampleRate: sampleRate}
|
||||||
|
return dev, dev.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
// send continuous measurement start command.
|
||||||
|
func (dev *Dev) start() error {
|
||||||
|
if err := dev.d.Tx(sampleRateCommands[dev.sampleRate], nil); err != nil {
|
||||||
|
return fmt.Errorf("hdc302x: init %w", err)
|
||||||
|
}
|
||||||
|
// Sleep for a minimum of one sample acquisition period. If you
|
||||||
|
// read before a sample has acquired, you get remote I/O error.
|
||||||
|
time.Sleep(sampleRateDurations[dev.sampleRate])
|
||||||
|
dev.halted = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the raw count to a temperature.
|
||||||
|
func countToTemperature(bytes []byte) physic.Temperature {
|
||||||
|
count := (uint16(bytes[0]) << 8) | uint16(bytes[1])
|
||||||
|
f := float64(count)/float64(scaleDivisor)*temperatureScalar + temperatureOffset
|
||||||
|
t := physic.ZeroCelsius + physic.Temperature(f*float64(physic.Celsius))
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the raw count to a humidity value.
|
||||||
|
func countToHumidity(bytes []byte) physic.RelativeHumidity {
|
||||||
|
count := (uint16(bytes[0]) << 8) | uint16(bytes[1])
|
||||||
|
f := float64(count) / float64(scaleDivisor) * humidityScalar
|
||||||
|
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 {
|
||||||
|
dev.mu.Lock()
|
||||||
|
defer dev.mu.Unlock()
|
||||||
|
if dev.shutdown != nil {
|
||||||
|
close(dev.shutdown)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if !dev.halted {
|
||||||
|
dev.halted = true
|
||||||
|
err = dev.d.Tx(stopContinuousReadings, nil)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sense reads temperature and humidity from the device and writes the value to
|
||||||
|
// the specified env variable. Implements physic.SenseEnv.
|
||||||
|
func (dev *Dev) Sense(env *physic.Env) error {
|
||||||
|
env.Temperature = 0
|
||||||
|
env.Pressure = 0
|
||||||
|
env.Humidity = 0
|
||||||
|
res := make([]byte, 6)
|
||||||
|
dev.mu.Lock()
|
||||||
|
defer dev.mu.Unlock()
|
||||||
|
if dev.halted {
|
||||||
|
if err := dev.start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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] {
|
||||||
|
return errInvalidCRC
|
||||||
|
}
|
||||||
|
env.Temperature = countToTemperature(res)
|
||||||
|
env.Humidity = countToHumidity(res[3:])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func temperatureToFloat64(temp physic.Temperature) float64 {
|
||||||
|
return float64(temp) / float64(physic.Celsius)
|
||||||
|
}
|
||||||
|
|
||||||
|
func humidityToFloat64(humidity physic.RelativeHumidity) float64 {
|
||||||
|
return float64(humidity) / float64(physic.PercentRH)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SenseContinuous continuously reads from the device and writes the value to
|
||||||
|
// the returned channel. Implements physic.SenseEnv. To terminate the
|
||||||
|
// continuous read, call Halt().
|
||||||
|
//
|
||||||
|
// If interval is less than the device sample period, an error is returned.
|
||||||
|
func (dev *Dev) SenseContinuous(interval time.Duration) (<-chan physic.Env, error) {
|
||||||
|
|
||||||
|
if dev.shutdown != nil {
|
||||||
|
return nil, errors.New("hdc302x: SenseContinuous already running")
|
||||||
|
}
|
||||||
|
|
||||||
|
if interval < sampleRateDurations[dev.sampleRate] {
|
||||||
|
return nil, errors.New("hdc302x: sample interval is < device sample rate")
|
||||||
|
}
|
||||||
|
|
||||||
|
dev.shutdown = make(chan struct{})
|
||||||
|
chResult := make(chan physic.Env, 16)
|
||||||
|
go func(ch chan physic.Env) {
|
||||||
|
ticker := time.NewTicker(interval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
defer close(ch)
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-dev.shutdown:
|
||||||
|
dev.shutdown = nil
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
env := physic.Env{}
|
||||||
|
if err := dev.Sense(&env); err == nil {
|
||||||
|
ch <- env
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(chResult)
|
||||||
|
return chResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Precision returns the sensor's precision, or minimum value between steps the
|
||||||
|
// device can measure. Refer to the datasheet for information on limits and
|
||||||
|
// accuracy.
|
||||||
|
func (dev *Dev) Precision(env *physic.Env) {
|
||||||
|
env.Temperature = physic.Temperature(math.Round(temperatureScalar / scaleDivisor * float64(physic.Celsius)))
|
||||||
|
env.Humidity = physic.RelativeHumidity(math.Round((float64(physic.PercentRH) * humidityScalar) / float64(scaleDivisor)))
|
||||||
|
env.Pressure = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Dev) readSerialNumber() int64 {
|
||||||
|
var result int64
|
||||||
|
cmd := []byte{0x36, 0x83}
|
||||||
|
r := make([]byte, 3)
|
||||||
|
// 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]) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
result = result<<16 | (int64(r[0])<<8 | int64(r[1]))
|
||||||
|
cmd[1] += 1 // Increment the register to the next one.
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the alert / clear threshold values from the device.
|
||||||
|
func (dev *Dev) readAlertValues(cfg *Configuration) error {
|
||||||
|
|
||||||
|
// Pair
|
||||||
|
// Low
|
||||||
|
// Temp
|
||||||
|
// Humidity
|
||||||
|
// High
|
||||||
|
// Temp
|
||||||
|
// Humidity
|
||||||
|
|
||||||
|
var cmds = []devCommand{readLowAlertThresholds, readHighAlertThresholds, readLowClearThresholds, readHighClearThresholds}
|
||||||
|
var pairs = [2]*ThresholdPair{&cfg.AlertThresholds, &cfg.ClearThresholds}
|
||||||
|
var threshold *Threshold
|
||||||
|
|
||||||
|
r := make([]byte, 3)
|
||||||
|
|
||||||
|
for ix, cmd := range cmds {
|
||||||
|
pair := pairs[ix>>1]
|
||||||
|
if ix%2 == 0 {
|
||||||
|
threshold = &pair.Low
|
||||||
|
} else {
|
||||||
|
threshold = &pair.High
|
||||||
|
}
|
||||||
|
|
||||||
|
err := dev.d.Tx(cmd, r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if crc8(r[:2]) != r[2] {
|
||||||
|
return errInvalidCRC
|
||||||
|
}
|
||||||
|
wValue := uint16(r[0])<<8 | uint16(r[1])
|
||||||
|
// The alert value is returned as a 16 bit words, where bits 0-8 are the
|
||||||
|
// Temperature value, and bits 9-15 are the Humidity. The temperature
|
||||||
|
// bits correspond to bits 7-15 of the temperature, and bits 9-15 of the
|
||||||
|
// humidity. Refer to the datasheet.
|
||||||
|
temp := &threshold.Temperature
|
||||||
|
humidity := &threshold.Humidity
|
||||||
|
*temp = physic.Temperature(((float64(uint16(wValue<<7)) * temperatureScalar) / scaleDivisor) * float64(physic.Celsius))
|
||||||
|
*humidity = physic.RelativeHumidity(((float64(wValue&0xfe00) * humidityScalar) / scaleDivisor) * float64(physic.PercentRH))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readOffsets returns temperature/humidity offset values stored to the device.
|
||||||
|
func (dev *Dev) readOffsets(cfg *Configuration) error {
|
||||||
|
r := make([]byte, 3)
|
||||||
|
if err := dev.d.Tx(readSetOffsets, r); err != nil {
|
||||||
|
return fmt.Errorf("hdc302x: %w", err)
|
||||||
|
}
|
||||||
|
if crc8(r[:2]) != r[2] {
|
||||||
|
return errInvalidCRC
|
||||||
|
}
|
||||||
|
|
||||||
|
// The result comes back as the humidity offset, followed by
|
||||||
|
// the temperature offset. The offsets are computed by summing
|
||||||
|
// the bits and applying the partial algorithm.
|
||||||
|
|
||||||
|
h := uint16((r[0] & 0x7f)) << 7
|
||||||
|
t := uint16((r[1] & 0x7f)) << 6
|
||||||
|
rh := float64(h) / scaleDivisor * humidityScalar
|
||||||
|
temp := (float64(t) * temperatureScalar) / scaleDivisor
|
||||||
|
|
||||||
|
if r[0]&0x80 == 0x00 {
|
||||||
|
rh *= -1.0
|
||||||
|
}
|
||||||
|
cfg.HumidityOffset = physic.RelativeHumidity(rh * float64(physic.PercentRH))
|
||||||
|
|
||||||
|
if r[1]&0x80 == 0x00 {
|
||||||
|
temp *= -1.0
|
||||||
|
}
|
||||||
|
cfg.TemperatureOffset = physic.Temperature(temp * float64(physic.Celsius))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Dev) readVendorID() (uint16, error) {
|
||||||
|
r := make([]byte, 3)
|
||||||
|
err := dev.d.Tx(readVendorID, r)
|
||||||
|
if err == nil {
|
||||||
|
vid := uint16(r[0])<<8 | uint16(r[1])
|
||||||
|
return vid, nil
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadStatus returns the device's status word, and if successful, clears the
|
||||||
|
// status. Refer to the Status* constants and the datasheet for interpretation.
|
||||||
|
func (dev *Dev) ReadStatus() (StatusWord, error) {
|
||||||
|
r := make([]byte, 3)
|
||||||
|
if err := dev.d.Tx(readStatus, r); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if crc8(r[:2]) != r[2] {
|
||||||
|
return 0, errInvalidCRC
|
||||||
|
}
|
||||||
|
_ = dev.d.Tx(clearStatus, nil)
|
||||||
|
return StatusWord(r[0])<<8 | StatusWord(r[1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the device's configuration settings. Includes alert values, offset
|
||||||
|
// values, and other information about the device.
|
||||||
|
func (dev *Dev) Configuration() (*Configuration, error) {
|
||||||
|
cfg := &Configuration{SampleRate: dev.sampleRate}
|
||||||
|
cfg.SerialNumber = dev.readSerialNumber()
|
||||||
|
err := dev.readOffsets(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if cfg.VendorID, err = dev.readVendorID(); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
if cfg.Status, err = dev.ReadStatus(); err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dev.readAlertValues(cfg)
|
||||||
|
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setOffsets writes temperature and humidity offsets to the device.
|
||||||
|
// Refer to the datasheet for information on offsets. The critical
|
||||||
|
// thing to know is that the smallest offsets are ~0.2%RH, and ~
|
||||||
|
// 0.2 degrees C.
|
||||||
|
func (dev *Dev) setOffsets(cfg *Configuration) error {
|
||||||
|
var w = []byte{readSetOffsets[0],
|
||||||
|
readSetOffsets[1],
|
||||||
|
computeHumidityOffsetByte(cfg.HumidityOffset),
|
||||||
|
computeTemperatureOffsetByte(cfg.TemperatureOffset),
|
||||||
|
0,
|
||||||
|
}
|
||||||
|
w[4] = crc8(w[2:4])
|
||||||
|
return dev.d.Tx(w, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refer to the datasheet. Essentially, the offsets are only a specific set of
|
||||||
|
// bit ranges.
|
||||||
|
func computeTemperatureOffsetByte(temp physic.Temperature) byte {
|
||||||
|
var res byte
|
||||||
|
fTemp := temperatureToFloat64(temp)
|
||||||
|
if fTemp >= 0 {
|
||||||
|
res |= 0x80
|
||||||
|
} else {
|
||||||
|
fTemp *= -1.0
|
||||||
|
}
|
||||||
|
for bit := 12; bit > 5; bit-- {
|
||||||
|
offset := (float64(int64(1)<<bit) * temperatureScalar) / scaleDivisor
|
||||||
|
if fTemp >= offset {
|
||||||
|
fTemp -= offset
|
||||||
|
res |= (1 << (bit - 6))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeHumidityOffsetByte(humidity physic.RelativeHumidity) byte {
|
||||||
|
var res byte
|
||||||
|
fHumidity := humidityToFloat64(humidity)
|
||||||
|
if fHumidity >= 0 {
|
||||||
|
res |= 0x80
|
||||||
|
} else {
|
||||||
|
fHumidity *= -1.0
|
||||||
|
}
|
||||||
|
for bit := 13; bit > 6; bit-- {
|
||||||
|
offset := (float64(int64(1)<<bit) * humidityScalar) / scaleDivisor
|
||||||
|
if fHumidity >= offset {
|
||||||
|
fHumidity -= offset
|
||||||
|
res |= (1 << (bit - 7))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset performs a soft-reset of the device.
|
||||||
|
func (dev *Dev) Reset() error {
|
||||||
|
dev.mu.Lock()
|
||||||
|
defer dev.mu.Unlock()
|
||||||
|
err := dev.d.Tx(reset, nil)
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// setThreshold sets a threshold pair for either alert, or clear alert.
|
||||||
|
// if typeAlert is true, it indicates the pair type is alert, otherwise
|
||||||
|
// it's clear alert.
|
||||||
|
func (dev *Dev) setThresholds(typeAlert bool, tp *ThresholdPair) error {
|
||||||
|
var cmds = [][]devCommand{{writeLowAlertThresholds, writeHighAlertThresholds},
|
||||||
|
{writeLowClearThresholds, writeHighClearThresholds}}
|
||||||
|
|
||||||
|
pair := 1
|
||||||
|
if typeAlert {
|
||||||
|
pair = 0
|
||||||
|
}
|
||||||
|
var th *Threshold
|
||||||
|
for ix := range 2 {
|
||||||
|
if ix == 0 {
|
||||||
|
th = &tp.Low
|
||||||
|
} else {
|
||||||
|
th = &tp.High
|
||||||
|
}
|
||||||
|
temp := temperatureToFloat64(th.Temperature)
|
||||||
|
tempBits := uint16(0)
|
||||||
|
for bit := 15; bit >= 0; bit-- {
|
||||||
|
bitVal := (float64(uint16(1<<bit)) * temperatureScalar) / scaleDivisor
|
||||||
|
if temp >= bitVal {
|
||||||
|
temp -= bitVal
|
||||||
|
tempBits |= (1 << bit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
humidity := humidityToFloat64(th.Humidity)
|
||||||
|
humBits := uint16(0)
|
||||||
|
for bit := 15; bit >= 0; bit-- {
|
||||||
|
bitVal := (float64(uint16(1<<bit)) * humidityScalar) / scaleDivisor
|
||||||
|
if humidity >= bitVal {
|
||||||
|
humidity -= bitVal
|
||||||
|
humBits |= (1 << bit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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])
|
||||||
|
err := dev.d.Tx(w, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfiguration takes a modified configuration struct and
|
||||||
|
// applies it to the device.
|
||||||
|
func (dev *Dev) SetConfiguration(cfg *Configuration) error {
|
||||||
|
_ = dev.Halt()
|
||||||
|
dev.mu.Lock()
|
||||||
|
defer dev.mu.Unlock()
|
||||||
|
current, err := dev.Configuration()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if current.HumidityOffset != cfg.HumidityOffset || current.TemperatureOffset != cfg.TemperatureOffset {
|
||||||
|
if err := dev.setOffsets(cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current.AlertThresholds.Equals(&cfg.AlertThresholds) {
|
||||||
|
if err := dev.setThresholds(true, &cfg.AlertThresholds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !current.ClearThresholds.Equals(&cfg.ClearThresholds) {
|
||||||
|
if err := dev.setThresholds(false, &cfg.ClearThresholds); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// The hdc302x sensors have a built in heater element for operating in environments
|
||||||
|
// where the humidity/temperature level is condensing. SetHeater allows you to turn
|
||||||
|
// the heater element on and off at specified power levels. Refer to the datasheet
|
||||||
|
// for instructions on how the heater can be used in those environments.
|
||||||
|
func (dev *Dev) SetHeater(powerLevel HeaterPower) error {
|
||||||
|
if powerLevel > PowerFull {
|
||||||
|
return fmt.Errorf("hdc302x: invalid value for powerLevel: 0x%x", powerLevel)
|
||||||
|
}
|
||||||
|
if powerLevel == PowerOff {
|
||||||
|
return dev.d.Tx(disableHeater, nil)
|
||||||
|
}
|
||||||
|
var setValue = []byte{readSetHeater[0],
|
||||||
|
readSetHeater[1],
|
||||||
|
byte((powerLevel >> 8) & 0xff),
|
||||||
|
byte(powerLevel & 0xff),
|
||||||
|
0}
|
||||||
|
setValue[4] = crc8(setValue[2:4])
|
||||||
|
err := dev.d.Tx(setValue, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return dev.d.Tx(enableHeater, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dev *Dev) String() string {
|
||||||
|
return fmt.Sprintf("hdc302x: %s", dev.d.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Configuration) String() string {
|
||||||
|
return fmt.Sprintf(`{
|
||||||
|
SerialNumber: 0x%x,
|
||||||
|
VendorID: 0x%x,
|
||||||
|
Status: 0x%x,
|
||||||
|
SampleRate: %d,
|
||||||
|
HumidityOffset: %s,
|
||||||
|
TemperatureOffset: %s,
|
||||||
|
AlertThresholds: %s,
|
||||||
|
ClearThresholds: %s
|
||||||
|
}`,
|
||||||
|
cfg.SerialNumber,
|
||||||
|
cfg.VendorID,
|
||||||
|
cfg.Status,
|
||||||
|
cfg.SampleRate,
|
||||||
|
cfg.HumidityOffset,
|
||||||
|
cfg.TemperatureOffset+physic.ZeroCelsius,
|
||||||
|
&cfg.AlertThresholds,
|
||||||
|
&cfg.ClearThresholds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Threshold) String() string {
|
||||||
|
return fmt.Sprintf("{ Humidity: %s, Temperature: %s }", t.Humidity, t.Temperature+physic.ZeroCelsius)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *ThresholdPair) String() string {
|
||||||
|
return fmt.Sprintf("{ Low: %s, High: %s }",
|
||||||
|
&tp.Low,
|
||||||
|
&tp.High)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Threshold) Equals(tCompare *Threshold) bool {
|
||||||
|
return t.Temperature == tCompare.Temperature && t.Humidity == tCompare.Humidity
|
||||||
|
}
|
||||||
|
|
||||||
|
// For thresholds, you can only set a truncated value. For temperature, that means
|
||||||
|
// the 9 high bits, and for humidity, the 7 high bits. This means a comparison of
|
||||||
|
// a written value with the resulting value can be off. This method encapsulates
|
||||||
|
// the comparison of a threshold pair to make sure they're approximately equal.
|
||||||
|
func (t *Threshold) ApproximatelyEquals(tCompare *Threshold) bool {
|
||||||
|
t1 := temperatureToFloat64(t.Temperature)
|
||||||
|
h1 := humidityToFloat64(t.Humidity)
|
||||||
|
t2 := temperatureToFloat64(tCompare.Temperature)
|
||||||
|
h2 := humidityToFloat64(tCompare.Humidity)
|
||||||
|
tLimit := float64(uint16(1<<8)) * temperatureScalar / scaleDivisor
|
||||||
|
hLimit := float64(uint16(1<<9)) * humidityScalar / scaleDivisor
|
||||||
|
return math.Abs(t1-t2) < tLimit &&
|
||||||
|
math.Abs(h1-h2) < hLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tp *ThresholdPair) Equals(tpCompare *ThresholdPair) bool {
|
||||||
|
return tp.Low.Equals(&tpCompare.Low) && tp.High.Equals(&tpCompare.High)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ conn.Resource = &Dev{}
|
||||||
|
var _ physic.SenseEnv = &Dev{}
|
||||||
@ -0,0 +1,620 @@
|
|||||||
|
// Copyright 2024 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 hdc302x
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"periph.io/x/conn/v3/i2c"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2creg"
|
||||||
|
"periph.io/x/conn/v3/i2c/i2ctest"
|
||||||
|
"periph.io/x/conn/v3/physic"
|
||||||
|
"periph.io/x/host/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var bus i2c.Bus
|
||||||
|
var liveDevice bool
|
||||||
|
|
||||||
|
// Playback values for a single sense operation.
|
||||||
|
var pbSense = []i2ctest.IO{
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x23, 0x34}},
|
||||||
|
// 26.621 C, 23.2%RH
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe0, 0x0}, R: []uint8{0x68, 0xc5, 0x51, 0x3b, 0x82, 0x31}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Playback for heater testing.
|
||||||
|
var pbHeater = []i2ctest.IO{
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x23, 0x34}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe0, 0x0}, R: []uint8{0x64, 0x93, 0x3d, 0x45, 0x3a, 0x61}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x6e, 0x3f, 0xff, 0x6}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x6d}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe0, 0x0}, R: []uint8{0x9d, 0xb1, 0x2, 0x9, 0xc6, 0xa3}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x66}}}
|
||||||
|
|
||||||
|
// Playback for modifying configuration.
|
||||||
|
var pbConfiguration = []i2ctest.IO{
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x23, 0x34}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x83}, R: []uint8{0xc2, 0x95, 0x3e}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x84}, R: []uint8{0xb1, 0x49, 0x51}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x85}, R: []uint8{0x15, 0x21, 0x2f}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4}, R: []uint8{0x80, 0x80, 0xd8}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x37, 0x81}, R: []uint8{0x30, 0x0, 0x33}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xf3, 0x2d}, R: []uint8{0x0, 0x0, 0x81}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x41}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x2}, R: []uint8{0x34, 0x66, 0xad}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x1f}, R: []uint8{0xcd, 0x33, 0xfd}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x9}, R: []uint8{0x38, 0x69, 0x37}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x14}, R: []uint8{0xc9, 0x2d, 0x22}}}
|
||||||
|
|
||||||
|
// playback for offset modification
|
||||||
|
var pbOffsets = []i2ctest.IO{
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x23, 0x34}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe0, 0x0}, R: []uint8{0x70, 0x90, 0x83, 0x42, 0x10, 0x92}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x83}, R: []uint8{0xc2, 0x95, 0x3e}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x84}, R: []uint8{0xb1, 0x49, 0x51}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x85}, R: []uint8{0x15, 0x21, 0x2f}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4}, R: []uint8{0x19, 0xba, 0x48}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x37, 0x81}, R: []uint8{0x30, 0x0, 0x33}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xf3, 0x2d}, R: []uint8{0x0, 0x0, 0x81}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x41}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x2}, R: []uint8{0x34, 0x66, 0xad}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x1f}, R: []uint8{0xcd, 0x33, 0xfd}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x9}, R: []uint8{0x38, 0x69, 0x37}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x14}, R: []uint8{0xc9, 0x2d, 0x22}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x93}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x83}, R: []uint8{0xc2, 0x95, 0x3e}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x84}, R: []uint8{0xb1, 0x49, 0x51}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x85}, R: []uint8{0x15, 0x21, 0x2f}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4}, R: []uint8{0x19, 0xba, 0x48}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x37, 0x81}, R: []uint8{0x30, 0x0, 0x33}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xf3, 0x2d}, R: []uint8{0x0, 0x0, 0x81}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x41}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x2}, R: []uint8{0x34, 0x66, 0xad}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x1f}, R: []uint8{0xcd, 0x33, 0xfd}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x9}, R: []uint8{0x38, 0x69, 0x37}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x14}, R: []uint8{0xc9, 0x2d, 0x22}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4, 0x32, 0xf4, 0xac}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x83}, R: []uint8{0xc2, 0x95, 0x3e}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x84}, R: []uint8{0xb1, 0x49, 0x51}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x85}, R: []uint8{0x15, 0x21, 0x2f}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4}, R: []uint8{0x32, 0xf4, 0xac}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x37, 0x81}, R: []uint8{0x30, 0x0, 0x33}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xf3, 0x2d}, R: []uint8{0x0, 0x0, 0x81}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x41}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x2}, R: []uint8{0x34, 0x66, 0xad}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x1f}, R: []uint8{0xcd, 0x33, 0xfd}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x9}, R: []uint8{0x38, 0x69, 0x37}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x14}, R: []uint8{0xc9, 0x2d, 0x22}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x23, 0x34}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe0, 0x0}, R: []uint8{0x7f, 0x28, 0x1c, 0x37, 0x8d, 0xab}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0xa2}}}
|
||||||
|
|
||||||
|
var pbAlerts = []i2ctest.IO{
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x23, 0x34}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe0, 0x0}, R: []uint8{0x70, 0xe0, 0x7b, 0x3f, 0xf7, 0xbf}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x83}, R: []uint8{0xc2, 0x95, 0x3e}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x84}, R: []uint8{0xb1, 0x49, 0x51}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x85}, R: []uint8{0x15, 0x21, 0x2f}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4}, R: []uint8{0x19, 0xba, 0x48}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x37, 0x81}, R: []uint8{0x30, 0x0, 0x33}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xf3, 0x2d}, R: []uint8{0x80, 0x10, 0xe1}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x41}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x2}, R: []uint8{0x34, 0x66, 0xad}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x1f}, R: []uint8{0xcd, 0x33, 0xfd}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x9}, R: []uint8{0x38, 0x69, 0x37}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x14}, R: []uint8{0xc9, 0x2d, 0x22}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x93}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x83}, R: []uint8{0xc2, 0x95, 0x3e}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x84}, R: []uint8{0xb1, 0x49, 0x51}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x85}, R: []uint8{0x15, 0x21, 0x2f}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4}, R: []uint8{0x19, 0xba, 0x48}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x37, 0x81}, R: []uint8{0x30, 0x0, 0x33}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xf3, 0x2d}, R: []uint8{0x0, 0x0, 0x81}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x41}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x2}, R: []uint8{0x34, 0x66, 0xad}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x1f}, R: []uint8{0xcd, 0x33, 0xfd}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x9}, R: []uint8{0x38, 0x69, 0x37}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x14}, R: []uint8{0xc9, 0x2d, 0x22}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4, 0x80, 0x80, 0xd8}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x61, 0x0, 0x4c, 0x1d, 0xb3}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x61, 0x1d, 0xbe, 0xdb, 0x93}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x61, 0xb, 0x58, 0x2b, 0x3d}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x61, 0x16, 0xb2, 0xcc, 0xf3}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x83}, R: []uint8{0xc2, 0x95, 0x3e}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x84}, R: []uint8{0xb1, 0x49, 0x51}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x36, 0x85}, R: []uint8{0x15, 0x21, 0x2f}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xa0, 0x4}, R: []uint8{0x80, 0x80, 0xd8}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x37, 0x81}, R: []uint8{0x30, 0x0, 0x33}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xf3, 0x2d}, R: []uint8{0x0, 0x0, 0x81}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x41}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x2}, R: []uint8{0x4c, 0x1d, 0xb3}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x1f}, R: []uint8{0xbe, 0xdb, 0x93}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x9}, R: []uint8{0x58, 0x2b, 0x3d}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe1, 0x14}, R: []uint8{0xb2, 0xcc, 0xf3}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x23, 0x34}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xe0, 0x0}, R: []uint8{0x62, 0x77, 0x62, 0x4a, 0x94, 0x1b}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0xf3, 0x2d}, R: []uint8{0x89, 0x0, 0x61}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x41}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0x93}},
|
||||||
|
{Addr: DefaultSensorAddress, W: []uint8{0x30, 0xa2}}}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
liveDevice = os.Getenv("HDC302X") != ""
|
||||||
|
|
||||||
|
// Make sure periph is initialized.
|
||||||
|
if _, err = host.Init(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if liveDevice {
|
||||||
|
bus, err = i2creg.Open("")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
// Add the recorder to dump the data stream when we're using a live device.
|
||||||
|
bus = &i2ctest.Record{Bus: bus}
|
||||||
|
} else {
|
||||||
|
bus = &i2ctest.Playback{DontPanic: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDev returns a configured device using either an i2c bus, or a playback bus.
|
||||||
|
func getDev(t *testing.T, playbackOps ...[]i2ctest.IO) (*Dev, error) {
|
||||||
|
if liveDevice {
|
||||||
|
if recorder, ok := bus.(*i2ctest.Record); ok {
|
||||||
|
// Clear the operations buffer.
|
||||||
|
recorder.Ops = make([]i2ctest.IO, 0, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if len(playbackOps) == 1 {
|
||||||
|
pb := bus.(*i2ctest.Playback)
|
||||||
|
pb.Ops = playbackOps[0]
|
||||||
|
pb.Count = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dev, err := NewI2C(bus, DefaultSensorAddress, RateFourHertz)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Log("error constructing dev")
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dev, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown dumps the recorder values if we we're running a live device.
|
||||||
|
func shutdown(t *testing.T) {
|
||||||
|
if recorder, ok := bus.(*i2ctest.Record); ok {
|
||||||
|
t.Logf("%#v", recorder.Ops)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
envPrecision := physic.Env{}
|
||||||
|
dev := Dev{}
|
||||||
|
dev.Precision(&envPrecision)
|
||||||
|
t.Logf("Precision: Temperature: %d nanoKelvin Humidity: %d TenthMicroPercent RH", envPrecision.Temperature, envPrecision.Humidity)
|
||||||
|
|
||||||
|
temp := countToTemperature([]byte{0x00, 0x00})
|
||||||
|
expected := physic.ZeroCelsius - 45*physic.Celsius
|
||||||
|
if temp != expected {
|
||||||
|
t.Errorf("unexpected countToTemperature. expected %s, received %s %d", expected, temp, temp)
|
||||||
|
}
|
||||||
|
temp = countToTemperature([]byte{0xd4, 0x1c})
|
||||||
|
expected = physic.ZeroCelsius + 100*physic.Celsius
|
||||||
|
if math.Abs(float64(expected-temp)) > float64(envPrecision.Temperature) {
|
||||||
|
t.Errorf("unexpected countToTemperature. expected %s (%d), received %s (%d)", expected, expected, temp, temp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var hTests = []struct {
|
||||||
|
bytes []byte
|
||||||
|
result physic.RelativeHumidity
|
||||||
|
}{
|
||||||
|
{bytes: []byte{0x0, 0x0}, result: physic.RelativeHumidity(0)},
|
||||||
|
{bytes: []byte{0x80, 0x0}, result: 50 * physic.PercentRH},
|
||||||
|
{bytes: []byte{0xff, 0xff}, result: 100 * physic.PercentRH},
|
||||||
|
}
|
||||||
|
for _, hTest := range hTests {
|
||||||
|
humidity := countToHumidity(hTest.bytes)
|
||||||
|
if (humidity - hTest.result) > envPrecision.Humidity {
|
||||||
|
t.Errorf("unexpected humidity got %s (%d) expected %s (%d)", humidity, humidity, hTest.result, hTest.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests the conversions of offsets to binary offset values used by the device.
|
||||||
|
func TestOffsetConversions(t *testing.T) {
|
||||||
|
// These are a subtly off from the datasheet because it's using floating point representation,
|
||||||
|
var tempOffsets = []struct {
|
||||||
|
temperature physic.Temperature
|
||||||
|
expectedResult byte
|
||||||
|
}{
|
||||||
|
{temperature: 170_902 * physic.MicroKelvin, expectedResult: 0x81},
|
||||||
|
{temperature: -170_902 * physic.MicroKelvin, expectedResult: 0x01}, // Test sign handling
|
||||||
|
{temperature: 7_178_000 * physic.MicroKelvin, expectedResult: 0xaa},
|
||||||
|
{temperature: 10_937_700 * physic.MicroKelvin, expectedResult: 0xc0},
|
||||||
|
{temperature: 21_704_500 * physic.MicroKelvin, expectedResult: 0xff},
|
||||||
|
}
|
||||||
|
for _, test := range tempOffsets {
|
||||||
|
res := computeTemperatureOffsetByte(test.temperature)
|
||||||
|
if res != test.expectedResult {
|
||||||
|
t.Errorf("computeTemperatureOffsetByte() Offset: %s Expected Value: 0x%x Received: 0x%x", test.temperature, test.expectedResult, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now repeat for humidity
|
||||||
|
var humidityOffsets = []struct {
|
||||||
|
humidity physic.RelativeHumidity
|
||||||
|
expectedResult byte
|
||||||
|
}{
|
||||||
|
{humidity: 19532 * physic.TenthMicroRH, expectedResult: 0x81},
|
||||||
|
{humidity: -19532 * physic.TenthMicroRH, expectedResult: 0x01},
|
||||||
|
{humidity: 820326 * physic.TenthMicroRH, expectedResult: 0xaa},
|
||||||
|
{humidity: -24_805_1 * physic.MicroRH, expectedResult: 0x7f},
|
||||||
|
}
|
||||||
|
for _, test := range humidityOffsets {
|
||||||
|
res := computeHumidityOffsetByte(test.humidity)
|
||||||
|
if res != test.expectedResult {
|
||||||
|
t.Errorf("computeHumidityOffsetByte() Offset: %s Expected Value: 0x%x Received: 0x%x", test.humidity, test.expectedResult, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBasic(t *testing.T) {
|
||||||
|
dev, err := getDev(t, []i2ctest.IO{pbSense[0]})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
env := &physic.Env{}
|
||||||
|
dev.Precision(env)
|
||||||
|
if env.Pressure != 0 {
|
||||||
|
t.Error("this device doesn't measure pressure")
|
||||||
|
}
|
||||||
|
if env.Temperature != (2670329 * physic.NanoKelvin) {
|
||||||
|
t.Errorf("incorrect temperature precision value got %d expected %d", env.Temperature, 2670329*physic.NanoKelvin)
|
||||||
|
}
|
||||||
|
if env.Humidity != 153*physic.TenthMicroRH {
|
||||||
|
t.Errorf("incorrect humidity precision got %d expected %d", env.Humidity, 153*physic.TenthMicroRH)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := dev.String()
|
||||||
|
if len(s) == 0 {
|
||||||
|
t.Error("invalid value for String()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSense(t *testing.T) {
|
||||||
|
d, err := getDev(t, pbSense)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to initialize hdc302x: %v", err)
|
||||||
|
}
|
||||||
|
defer shutdown(t)
|
||||||
|
|
||||||
|
// Read temperature and humidity from the sensor
|
||||||
|
e := physic.Env{}
|
||||||
|
if err := d.Sense(&e); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Logf("%8s %9s", e.Temperature, e.Humidity)
|
||||||
|
|
||||||
|
if !liveDevice {
|
||||||
|
// The playback temp is 26.621C Ensure that's what we got.
|
||||||
|
expected := physic.Temperature(299770889600)
|
||||||
|
if e.Temperature != expected {
|
||||||
|
t.Errorf("incorrect temperature value read. Expected: %s (%d) Found: %s (%d)",
|
||||||
|
expected.String(),
|
||||||
|
expected,
|
||||||
|
e.Temperature.String(),
|
||||||
|
e.Temperature,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 23.2% expected.
|
||||||
|
expectedRH := 2324559 * physic.TenthMicroRH
|
||||||
|
if e.Humidity != expectedRH {
|
||||||
|
t.Errorf("incorrect humidity value read. Expected: %s (%d) Found: %s (%d)",
|
||||||
|
expectedRH.String(),
|
||||||
|
expectedRH,
|
||||||
|
e.Humidity.String(),
|
||||||
|
e.Humidity,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSenseContinuous(t *testing.T) {
|
||||||
|
|
||||||
|
readCount := int32(10)
|
||||||
|
|
||||||
|
// make 10 copies of the single reading playback data.
|
||||||
|
pb := make([]i2ctest.IO, 0, readCount+1)
|
||||||
|
pb = append(pb, pbSense[0])
|
||||||
|
for range readCount {
|
||||||
|
pb = append(pb, pbSense[1])
|
||||||
|
}
|
||||||
|
// Add in the halt
|
||||||
|
pb = append(pb, i2ctest.IO{Addr: DefaultSensorAddress,
|
||||||
|
W: []uint8{stopContinuousReadings[0], stopContinuousReadings[1]}})
|
||||||
|
|
||||||
|
dev, err := getDev(t, pb)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(fmt.Errorf("failed to initialize hd302x: %w", err))
|
||||||
|
}
|
||||||
|
defer shutdown(t)
|
||||||
|
|
||||||
|
_, err = dev.SenseContinuous(100 * time.Millisecond)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error for sense continuous interval < sample interval")
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, err := dev.SenseContinuous(time.Second)
|
||||||
|
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)*1000
|
||||||
|
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.
|
||||||
|
if counter.Load() == readCount || time.Now().UnixMilli() > tEnd {
|
||||||
|
err := dev.Halt()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
wg.Done()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for e := range ch {
|
||||||
|
counter.Add(1)
|
||||||
|
t.Log(time.Now(), e)
|
||||||
|
}
|
||||||
|
if counter.Load() != readCount {
|
||||||
|
t.Errorf("expected %d readings. received %d", readCount, counter.Load())
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfiguration(t *testing.T) {
|
||||||
|
dev, err := getDev(t, pbConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to initialize hd302x: %v", err)
|
||||||
|
}
|
||||||
|
defer shutdown(t)
|
||||||
|
|
||||||
|
cfg, err := dev.Configuration()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
s := cfg.String()
|
||||||
|
t.Log("configuration: ", s)
|
||||||
|
if len(s) == 0 {
|
||||||
|
t.Errorf("invalid Configuration.String()")
|
||||||
|
}
|
||||||
|
if cfg.SerialNumber == 0 {
|
||||||
|
t.Error("invalid serial number")
|
||||||
|
}
|
||||||
|
if cfg.VendorID != 0x3000 {
|
||||||
|
t.Errorf("invalid manufacturer id 0x%x", cfg.VendorID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests applying offsets for temperature and humidity and checking that
|
||||||
|
// the values are applied during a subsequent read.
|
||||||
|
func TestOffsetModification(t *testing.T) {
|
||||||
|
dev, err := getDev(t, pbOffsets)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to initialize hd302x: %v", err)
|
||||||
|
}
|
||||||
|
defer shutdown(t)
|
||||||
|
env := physic.Env{}
|
||||||
|
err = dev.Sense(&env)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("Initial Readings: %s", env)
|
||||||
|
cfg, err := dev.Configuration()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cfg.TemperatureOffset += 10 * physic.Celsius
|
||||||
|
cfg.HumidityOffset -= 5 * physic.PercentRH
|
||||||
|
t.Log("writing configuration: ", cfg)
|
||||||
|
err = dev.SetConfiguration(cfg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
cfg2, _ := dev.Configuration()
|
||||||
|
t.Logf("read configuration=%s", cfg2)
|
||||||
|
env2 := physic.Env{}
|
||||||
|
err = dev.Sense(&env2)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log("Second Readings (post offset): ", env2)
|
||||||
|
if env2.Temperature < (env.Temperature+(9_500*physic.MilliKelvin)) ||
|
||||||
|
env2.Temperature > (env.Temperature+(10_500*physic.MilliKelvin)) {
|
||||||
|
t.Errorf("offset temperature invalid. Expected ~ %s + 10C Got: %s", env.Temperature, env2.Temperature)
|
||||||
|
}
|
||||||
|
|
||||||
|
lLow := env.Humidity - 6*physic.PercentRH
|
||||||
|
lHigh := env.Humidity - 4*physic.PercentRH
|
||||||
|
if (env2.Humidity < lLow) ||
|
||||||
|
(env2.Humidity > lHigh) {
|
||||||
|
t.Errorf("offset humidity invalid. Expected ~ %s - 5%% Got: %s Lower Limit: %s, Upper Limit: %s", env.Humidity, env2.Humidity, lLow, lHigh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue a soft reset to make sure any alterations are undone.
|
||||||
|
_ = dev.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests using alert values.
|
||||||
|
func TestAlerts(t *testing.T) {
|
||||||
|
|
||||||
|
dev, err := getDev(t, pbAlerts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to initialize hd302x: %v", err)
|
||||||
|
}
|
||||||
|
defer shutdown(t)
|
||||||
|
env := physic.Env{}
|
||||||
|
err = dev.Sense(&env)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("Initial Temperature/Humidity Readings: %s", env)
|
||||||
|
cfg, err := dev.Configuration()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.TemperatureOffset = 0
|
||||||
|
cfg.HumidityOffset = 0
|
||||||
|
cfg.AlertThresholds.Low.Temperature = 10 * physic.Celsius
|
||||||
|
cfg.AlertThresholds.High.Temperature = 75 * physic.Celsius
|
||||||
|
// Purposely set the low limit so it will trigger an alert on the status bit.
|
||||||
|
cfg.AlertThresholds.Low.Humidity = env.Humidity + 5*physic.PercentRH
|
||||||
|
cfg.AlertThresholds.High.Humidity = 75 * physic.PercentRH
|
||||||
|
|
||||||
|
cfg.ClearThresholds.Low.Temperature = 15 * physic.Celsius
|
||||||
|
cfg.ClearThresholds.High.Temperature = 70 * physic.Celsius
|
||||||
|
cfg.ClearThresholds.Low.Humidity = cfg.AlertThresholds.Low.Humidity + 5*physic.PercentRH
|
||||||
|
cfg.ClearThresholds.High.Humidity = 70 * physic.PercentRH
|
||||||
|
t.Logf("Writing Configuration:\n%s", cfg)
|
||||||
|
// write the Alert levels
|
||||||
|
err = dev.SetConfiguration(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Re-read the configuration.
|
||||||
|
cfg2, err := dev.Configuration()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("re-read configuration = \n%s", cfg2)
|
||||||
|
// Trigger a read and then get the status word. We should have a humidity alert set...
|
||||||
|
_ = dev.Sense(&env)
|
||||||
|
status, err := dev.ReadStatus()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_ = dev.Halt()
|
||||||
|
|
||||||
|
// Verify things are within one lsb
|
||||||
|
if !cfg.AlertThresholds.Low.ApproximatelyEquals(&cfg2.AlertThresholds.Low) {
|
||||||
|
t.Errorf("error in low alert thresholds set: %s read: %s", cfg.AlertThresholds.Low.String(), cfg2.AlertThresholds.Low.String())
|
||||||
|
}
|
||||||
|
if !cfg.AlertThresholds.High.ApproximatelyEquals(&cfg2.AlertThresholds.High) {
|
||||||
|
t.Errorf("error in high alert thresholds set: %s read: %s", cfg.AlertThresholds.High.String(), cfg2.AlertThresholds.High.String())
|
||||||
|
}
|
||||||
|
if !cfg.ClearThresholds.Low.ApproximatelyEquals(&cfg2.ClearThresholds.Low) {
|
||||||
|
t.Errorf("error in low clear thresholds set: %s read: %s", cfg.ClearThresholds.Low.String(), cfg2.ClearThresholds.Low.String())
|
||||||
|
}
|
||||||
|
if !cfg.ClearThresholds.High.ApproximatelyEquals(&cfg2.ClearThresholds.High) {
|
||||||
|
t.Errorf("error in high clear thresholds set: %s read: %s", cfg.ClearThresholds.High.String(), cfg2.ClearThresholds.High.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if status&StatusActiveAlerts != StatusActiveAlerts {
|
||||||
|
t.Error("expected status active alerts to be set")
|
||||||
|
}
|
||||||
|
if status&StatusRHTrackingAlert != StatusRHTrackingAlert {
|
||||||
|
t.Error("expected rh tracking alerts to be set")
|
||||||
|
}
|
||||||
|
if status&StatusRHLowTrackingAlert != StatusRHLowTrackingAlert {
|
||||||
|
t.Error("expected RH Low Tracking alert status bit to be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue a soft reset to make sure any alterations are undone.
|
||||||
|
_ = dev.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHeater turns on the sensor's integrated heater. The heater
|
||||||
|
// can be used to remove condensation from the sensor.
|
||||||
|
func TestHeater(t *testing.T) {
|
||||||
|
dev, err := getDev(t, pbHeater)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to initialize hd302x: %v", err)
|
||||||
|
}
|
||||||
|
defer shutdown(t)
|
||||||
|
|
||||||
|
err = dev.SetHeater(PowerFull + 1)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error with invalid power value")
|
||||||
|
}
|
||||||
|
env := physic.Env{}
|
||||||
|
env2 := physic.Env{}
|
||||||
|
err = dev.Sense(&env)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Logf("initial temperature: %s Humidity: %s", env.Temperature, env.Humidity)
|
||||||
|
err = dev.SetHeater(PowerFull)
|
||||||
|
defer func() {
|
||||||
|
if errOff := dev.SetHeater(PowerOff); errOff != nil {
|
||||||
|
t.Error(errOff)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if liveDevice {
|
||||||
|
for range 5 {
|
||||||
|
t.Log("Sleeping 5 seconds to test heater...")
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = dev.Sense(&env2)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Logf("final temperature after heater enabled: %s Humidity: %s", env2.Temperature, env2.Humidity)
|
||||||
|
if env2.Temperature <= env.Temperature {
|
||||||
|
t.Errorf("expected heater to increase sensor temperature. Initial: %s Final: %s", env.Temperature, env2.Temperature)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue