add dht22 support

pull/111/head
Voroshilin Andrey 5 months ago
parent 55d75a38ed
commit b86c344570

@ -40,4 +40,5 @@ Benoit Pereira da Silva <benoit.pereiradasilva@gmail.com>
Stephan Sperber <sperberstephan@googlemail.com>
Thorsten von Eicken <tve@voneicken.com>
Weston Schmidt <weston_schmidt@alumni.purdue.edu>
Voroshilin Andrey <voroshilin.andrey.08@gmail.com>

@ -0,0 +1,161 @@
// Package dht22 reads temperature and humidity from DHT22/AM2302 sensors.
//
// Datasheet: https://www.adafruit.com/datasheets/DHT22.pdf
package dht22
import (
"errors"
"sync"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/physic"
"periph.io/x/conn/v3/pin"
)
// Dev represents a DHT22/AM2302 sensor.
type Dev struct {
pin gpio.PinIO
mu sync.Mutex
}
// New opens a connection to a DHT22 sensor on the given GPIO pin.
func New(p gpio.PinIO) (*Dev, error) {
if err := p.In(gpio.PullUp, gpio.FallingEdge); err != nil {
return nil, err
}
return &Dev{pin: p}, nil
}
// SenseContinuous returns a channel that will receive measurements at the
// specified interval. The channel is closed when stop is closed.
func (d *Dev) SenseContinuous(interval time.Duration, stop <-chan struct{}) (<-chan physic.Env, error) {
ch := make(chan physic.Env)
go func() {
defer close(ch)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-stop:
return
case <-ticker.C:
if env, err := d.Read(); err == nil {
select {
case ch <- env:
case <-stop:
return
}
}
}
}
}()
return ch, nil
}
// Read reads temperature and humidity from the sensor.
// Reading more frequently than once per 2 seconds may cause errors.
func (d *Dev) Read() (physic.Env, error) {
d.mu.Lock()
defer d.mu.Unlock()
// Prepare pin for output
if err := d.pin.Out(gpio.Low); err != nil {
return physic.Env{}, err
}
// Send start signal: pull low for 1-10ms, then pull high for 20-40µs
d.pin.Out(gpio.Low)
time.Sleep(2 * time.Millisecond)
d.pin.Out(gpio.High)
time.Sleep(40 * time.Microsecond)
// Switch to input with pull-up
if err := d.pin.In(gpio.PullUp, gpio.NoEdge); err != nil {
return physic.Env{}, err
}
// Wait for sensor response
if !waitForState(d.pin, false, 80*time.Microsecond) {
return physic.Env{}, errors.New("dht22: no response from sensor")
}
if !waitForState(d.pin, true, 80*time.Microsecond) {
return physic.Env{}, errors.New("dht22: response timeout")
}
// Read 40 bits of data (humidity, temperature, checksum)
var data [5]byte
for i := 0; i < 40; i++ {
if !waitForState(d.pin, false, 50*time.Microsecond) {
return physic.Env{}, errors.New("dht22: data sync error")
}
// High pulse length determines bit value
start := time.Now()
waitForState(d.pin, true, 70*time.Microsecond)
duration := time.Since(start)
bit := byte(i / 8)
shift := 7 - (i % 8)
if duration > 50*time.Microsecond {
data[bit] |= 1 << shift
}
}
// Verify checksum
checksum := data[0] + data[1] + data[2] + data[3]
if data[4] != checksum {
return physic.Env{}, errors.New("dht22: checksum error")
}
// Parse data (big-endian)
humidityInt := uint16(data[0])<<8 | uint16(data[1])
tempRaw := int16(uint16(data[2])<<8 | uint16(data[3]))
// Convert to physic units
// humidity is 0.1% RH units, so divide by 10 to get percent
humidity := physic.RelativeHumidity(float64(humidityInt)/10.0) * physic.PercentRH
// temperature is 0.1°C units, with bit 15 for sign
// Convert to nano Kelvin (base unit for physic.Temperature)
// 1°C = 1K in delta, but physic uses nanoKelvin
temperature := physic.Temperature(float64(tempRaw)/10.0) * physic.Celsius
env := physic.Env{
Humidity: humidity,
Temperature: temperature,
}
return env, nil
}
// String implements conn.Resource.
func (d *Dev) String() string {
return "DHT22{" + d.pin.String() + "}"
}
// Halt implements conn.Resource.
func (d *Dev) Halt() error {
return d.pin.Halt()
}
// Pin returns the GPIO pin used by the sensor.
func (d *Dev) Pin() pin.Pin {
return d.pin
}
// waitForState waits for pin to reach desired state within timeout.
func waitForState(p gpio.PinIO, state bool, timeout time.Duration) bool {
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
if p.Read() == gpio.Level(state) {
return true
}
time.Sleep(1 * time.Microsecond)
}
return false
}
// Ensure Dev implements conn.Resource.
var _ conn.Resource = &Dev{}
Loading…
Cancel
Save