Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS
Original file line number Diff line number Diff line change
Expand Up @@ -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>

161 changes: 161 additions & 0 deletions dht22/dht22.go
Original file line number Diff line number Diff line change
@@ -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]

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: windows-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: windows-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: windows-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: macos-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: macos-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: macos-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: ubuntu-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: ubuntu-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 107 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: ubuntu-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)
if data[4] != checksum {

Check failure on line 108 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: windows-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 108 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: macos-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)

Check failure on line 108 in dht22/dht22.go

View workflow job for this annotation

GitHub Actions / lint: ubuntu-latest

[CWE-118] slice index out of range (Rule:G602, Severity:LOW, Confidence:HIGH)
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