From b86c344570f5e9a6225c676bb25b28de36108a4c Mon Sep 17 00:00:00 2001 From: Voroshilin Andrey Date: Thu, 18 Dec 2025 18:54:37 +0300 Subject: [PATCH] add dht22 support --- CONTRIBUTORS | 1 + dht22/dht22.go | 161 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 dht22/dht22.go diff --git a/CONTRIBUTORS b/CONTRIBUTORS index cab4994..b1e7678 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -40,4 +40,5 @@ Benoit Pereira da Silva Stephan Sperber Thorsten von Eicken Weston Schmidt +Voroshilin Andrey diff --git a/dht22/dht22.go b/dht22/dht22.go new file mode 100644 index 0000000..0cec9ac --- /dev/null +++ b/dht22/dht22.go @@ -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{} \ No newline at end of file