├── globals.go ├── dhtWindows.go ├── LICENSE ├── README.md ├── dhtNotWindows.go └── dht.go /globals.go: -------------------------------------------------------------------------------- 1 | package dht 2 | 3 | import ( 4 | "time" 5 | 6 | "periph.io/x/conn/v3/gpio" 7 | ) 8 | 9 | // TemperatureUnit is the temperature unit wanted, either Celsius or Fahrenheit 10 | type TemperatureUnit int 11 | 12 | const ( 13 | // Celsius temperature unit 14 | Celsius TemperatureUnit = iota 15 | // Fahrenheit temperature unit 16 | Fahrenheit 17 | ) 18 | 19 | // DHT struct to interface with the sensor. 20 | // Call NewDHT to create a new one. 21 | type DHT struct { 22 | pin gpio.PinIO 23 | temperatureUnit TemperatureUnit 24 | sensorType string 25 | numErrors int 26 | lastRead time.Time 27 | } 28 | -------------------------------------------------------------------------------- /dhtWindows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package dht 5 | 6 | import ( 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // NewDHT to create a new DHT struct. 12 | // sensorType is dht11 for DHT11, anything else for AM2302 / DHT22. 13 | func NewDHT(pinName string, temperatureUnit TemperatureUnit, sensorType string) (*DHT, error) { 14 | dht := &DHT{temperatureUnit: temperatureUnit} 15 | 16 | // set sensorType 17 | sensorType = strings.ToLower(sensorType) 18 | if sensorType == "dht11" { 19 | dht.sensorType = "dht11" 20 | } 21 | 22 | // set lastRead a second before to give the pin a second to warm up 23 | dht.lastRead = time.Now().Add(-1 * time.Second) 24 | 25 | return dht, nil 26 | } 27 | 28 | // readBits will get the bits for humidity and temperature 29 | func (dht *DHT) readBits() ([]int, error) { 30 | // set lastRead so do not read more than once every 2 seconds 31 | dht.lastRead = time.Now() 32 | 33 | bits := make([]int, 40) 34 | 35 | return bits, nil 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MichaelS11 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go DHT22 / AM2302 / DHT11 interface 2 | 3 | Golang DHT22 / AM2302 / DHT11 interface using periph.io driver 4 | 5 | [![GoDoc Reference](https://godoc.org/github.com/MichaelS11/go-dht?status.svg)](http://godoc.org/github.com/MichaelS11/go-dht) 6 | [![Go Report Card](https://goreportcard.com/badge/github.com/MichaelS11/go-dht)](https://goreportcard.com/report/github.com/MichaelS11/go-dht) 7 | 8 | 9 | ## Please note 10 | 11 | Please make sure to setup your DHT22 / AM2302 / DHT11 correctly. Do a search on the internet to find guide. Here is an example of a guide: 12 | 13 | https://learn.adafruit.com/dht/connecting-to-a-dhtxx-sensor 14 | 15 | The examples below are from using a Raspberry Pi 3 with GPIO 19 for the pin. Your setup may be different, if so, your pin names would need to change in each example. 16 | 17 | Side note, in my testing the sensor has a fairly high level of read errors, suggest using ReadRetry or ReadBackground rather then just Read. 18 | 19 | Tested on Raspberry Pi 3 with AM2302. Please open an issue if there are any issues. 20 | 21 | 22 | ## Get 23 | 24 | go get github.com/MichaelS11/go-dht 25 | 26 | 27 | ## ReadRetry example 28 | 29 | ```go 30 | package main 31 | 32 | import ( 33 | "fmt" 34 | 35 | "github.com/MichaelS11/go-dht" 36 | ) 37 | 38 | func main() { 39 | err := dht.HostInit() 40 | if err != nil { 41 | fmt.Println("HostInit error:", err) 42 | return 43 | } 44 | 45 | dht, err := dht.NewDHT("GPIO19", dht.Fahrenheit, "") 46 | if err != nil { 47 | fmt.Println("NewDHT error:", err) 48 | return 49 | } 50 | 51 | humidity, temperature, err := dht.ReadRetry(11) 52 | if err != nil { 53 | fmt.Println("Read error:", err) 54 | return 55 | } 56 | 57 | fmt.Printf("humidity: %v\n", humidity) 58 | fmt.Printf("temperature: %v\n", temperature) 59 | } 60 | ``` 61 | 62 | 63 | ## ReadBackground example 64 | 65 | ```go 66 | package main 67 | 68 | import ( 69 | "fmt" 70 | "time" 71 | 72 | "github.com/MichaelS11/go-dht" 73 | ) 74 | 75 | func main() { 76 | err := dht.HostInit() 77 | if err != nil { 78 | fmt.Println("HostInit error:", err) 79 | return 80 | } 81 | 82 | dht, err := dht.NewDHT("GPIO19", dht.Fahrenheit, "") 83 | if err != nil { 84 | fmt.Println("NewDHT error:", err) 85 | return 86 | } 87 | 88 | stop := make(chan struct{}) 89 | stopped := make(chan struct{}) 90 | var humidity float64 91 | var temperature float64 92 | 93 | // get sensor reading every 20 seconds in background 94 | go dht.ReadBackground(&humidity, &temperature, 20*time.Second, stop, stopped) 95 | 96 | // should have at least read the sensor twice after 30 seconds 97 | time.Sleep(30 * time.Second) 98 | 99 | fmt.Printf("humidity: %v\n", humidity) 100 | fmt.Printf("temperature: %v\n", temperature) 101 | 102 | // to stop ReadBackground after done with reading, close the stop channel 103 | close(stop) 104 | 105 | // can check stopped channel to know when ReadBackground has stopped 106 | <-stopped 107 | } 108 | ``` 109 | 110 | 111 | ## Read example 112 | 113 | ```go 114 | package main 115 | 116 | import ( 117 | "fmt" 118 | 119 | "github.com/MichaelS11/go-dht" 120 | ) 121 | 122 | func main() { 123 | err := dht.HostInit() 124 | if err != nil { 125 | fmt.Println("HostInit error:", err) 126 | return 127 | } 128 | 129 | dht, err := dht.NewDHT("GPIO19", dht.Fahrenheit, "") 130 | if err != nil { 131 | fmt.Println("NewDHT error:", err) 132 | return 133 | } 134 | 135 | humidity, temperature, err := dht.Read() 136 | if err != nil { 137 | fmt.Println("Read error:", err) 138 | return 139 | } 140 | 141 | fmt.Printf("humidity: %v\n", humidity) 142 | fmt.Printf("temperature: %v\n", temperature) 143 | } 144 | ``` 145 | -------------------------------------------------------------------------------- /dhtNotWindows.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package dht 5 | 6 | import ( 7 | "fmt" 8 | "runtime/debug" 9 | "strings" 10 | "time" 11 | 12 | "periph.io/x/conn/v3/gpio" 13 | "periph.io/x/conn/v3/gpio/gpioreg" 14 | ) 15 | 16 | // NewDHT to create a new DHT struct. 17 | // sensorType is dht11 for DHT11, anything else for AM2302 / DHT22. 18 | func NewDHT(pinName string, temperatureUnit TemperatureUnit, sensorType string) (*DHT, error) { 19 | dht := &DHT{temperatureUnit: temperatureUnit} 20 | 21 | // set sensorType 22 | sensorType = strings.ToLower(sensorType) 23 | if sensorType == "dht11" { 24 | dht.sensorType = "dht11" 25 | } 26 | 27 | // get pin 28 | dht.pin = gpioreg.ByName(pinName) 29 | if dht.pin == nil { 30 | return nil, fmt.Errorf("pin is nill") 31 | } 32 | 33 | // set pin to high so ready for first read 34 | err := dht.pin.Out(gpio.High) 35 | if err != nil { 36 | return nil, fmt.Errorf("pin out high error: %v", err) 37 | } 38 | 39 | // set lastRead a second before to give the pin a second to warm up 40 | dht.lastRead = time.Now().Add(-1 * time.Second) 41 | 42 | return dht, nil 43 | } 44 | 45 | // readBits will get the bits for humidity and temperature 46 | func (dht *DHT) readBits() ([]int, error) { 47 | // create variables ahead of time before critical timing part 48 | var i int 49 | var startTime time.Time 50 | var levelPrevious gpio.Level 51 | var level gpio.Level 52 | levels := make([]gpio.Level, 0, 84) 53 | durations := make([]time.Duration, 0, 84) 54 | 55 | // set lastRead so do not read more than once every 2 seconds 56 | dht.lastRead = time.Now() 57 | 58 | // disable garbage collection during critical timing part 59 | gcPercent := debug.SetGCPercent(-1) 60 | 61 | // send start low 62 | err := dht.pin.Out(gpio.Low) 63 | if err != nil { 64 | dht.pin.Out(gpio.High) 65 | return nil, fmt.Errorf("pin out low error: %v", err) 66 | } 67 | time.Sleep(time.Millisecond) 68 | 69 | // send start high 70 | err = dht.pin.In(gpio.PullUp, gpio.NoEdge) 71 | if err != nil { 72 | dht.pin.Out(gpio.High) 73 | return nil, fmt.Errorf("pin in error: %v", err) 74 | } 75 | 76 | // read levels and durations with busy read 77 | // hope there is a better way in the future 78 | // tried to use WaitForEdge but seems to miss edges and/or take too long to detect them 79 | // note that pin read takes around .2 microsecond (us) on Raspberry PI 3 80 | // note that 1000 microsecond (us) = 1 millisecond (ms) 81 | levelPrevious = dht.pin.Read() 82 | level = levelPrevious 83 | for i = 0; i < 84; i++ { 84 | startTime = time.Now() 85 | for levelPrevious == level && time.Since(startTime) < time.Millisecond { 86 | level = dht.pin.Read() 87 | } 88 | durations = append(durations, time.Since(startTime)) 89 | levels = append(levels, levelPrevious) 90 | levelPrevious = level 91 | } 92 | 93 | // enable garbage collection, done with critical part 94 | debug.SetGCPercent(gcPercent) 95 | 96 | // set pin to high so ready for next time 97 | err = dht.pin.Out(gpio.High) 98 | if err != nil { 99 | return nil, fmt.Errorf("pin out high error: %v", err) 100 | } 101 | 102 | // get last low reading so know start of data 103 | var endNumber int 104 | for i = len(levels) - 1; ; i-- { 105 | if levels[i] == gpio.Low { 106 | endNumber = i 107 | break 108 | } 109 | if i < 80 { 110 | // not enough readings, i = 79 means endNumber is 78 or less 111 | return nil, fmt.Errorf("missing some readings - low level not found") 112 | } 113 | } 114 | startNumber := endNumber - 79 115 | 116 | // covert pulses into bits and check high levels 117 | bits := make([]int, 40) 118 | index := 0 119 | for i = startNumber; i < endNumber; i += 2 { 120 | // check high levels 121 | if levels[i] != gpio.High { 122 | return nil, fmt.Errorf("missing some readings - level not high") 123 | } 124 | // high should not be longer then 90 microseconds 125 | if durations[i] > 90*time.Microsecond { 126 | return nil, fmt.Errorf("missing some readings - high level duration too long: %v", durations[i]) 127 | } 128 | // bit is 0 if less than or equal to 30 microseconds 129 | if durations[i] > 30*time.Microsecond { 130 | // bit is 1 if more than 30 microseconds 131 | bits[index] = 1 132 | } 133 | index++ 134 | } 135 | 136 | // check low levels 137 | for i = startNumber + 1; i < endNumber+1; i += 2 { 138 | // check low levels 139 | if levels[i] != gpio.Low { 140 | return nil, fmt.Errorf("missing some readings - level not low") 141 | } 142 | // low should not be longer then 70 microseconds 143 | if durations[i] > 70*time.Microsecond { 144 | return nil, fmt.Errorf("missing some readings - low level duration too long: %v", durations[i]) 145 | } 146 | // low should not be shorter than 35 microseconds 147 | if durations[i] < 35*time.Microsecond { 148 | return nil, fmt.Errorf("missing some readings - low level duration too short: %v", durations[i]) 149 | } 150 | } 151 | 152 | return bits, nil 153 | } 154 | -------------------------------------------------------------------------------- /dht.go: -------------------------------------------------------------------------------- 1 | package dht 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "periph.io/x/host/v3" 8 | ) 9 | 10 | // HostInit calls periph.io host.Init(). This needs to be done before DHT can be used. 11 | func HostInit() error { 12 | _, err := host.Init() 13 | return err 14 | } 15 | 16 | // Read reads the sensor once, returing humidity and temperature, or an error. 17 | // Note that Read will sleep for at least 2 seconds between last call. 18 | // Each reads error adds a half second to sleep time to max of 30 seconds. 19 | func (dht *DHT) Read() (humidity float64, temperature float64, err error) { 20 | // set sleepTime 21 | var sleepTime time.Duration 22 | if dht.numErrors < 57 { 23 | sleepTime = (2 * time.Second) + (time.Duration(dht.numErrors) * 500 * time.Millisecond) 24 | } else { 25 | // sleep max of 30 seconds 26 | sleepTime = 30 * time.Second 27 | } 28 | sleepTime -= time.Since(dht.lastRead) 29 | 30 | // sleep between 2 and 30 seconds 31 | time.Sleep(sleepTime) 32 | 33 | // read bits from sensor 34 | var bits []int 35 | bits, err = dht.readBits() 36 | if err != nil { 37 | return 38 | } 39 | 40 | // covert bits to humidity and temperature 41 | humidity, temperature, err = dht.bitsToValues(bits) 42 | 43 | return 44 | } 45 | 46 | // bitsToValues will convert the bits into humidity and temperature values 47 | func (dht *DHT) bitsToValues(bits []int) (humidity float64, temperature float64, err error) { 48 | var sum8 uint8 49 | var sumTotal uint8 50 | var checkSum uint8 51 | var i int 52 | var humidityInt int 53 | var temperatureInt int 54 | 55 | // get humidityInt value 56 | for i = 0; i < 16; i++ { 57 | humidityInt = humidityInt << 1 58 | humidityInt += bits[i] 59 | // sum 8 bits for checkSum 60 | sum8 = sum8 << 1 61 | sum8 += uint8(bits[i]) 62 | if i == 7 || i == 15 { 63 | // got 8 bits, add to sumTotal for checkSum 64 | sumTotal += sum8 65 | sum8 = 0 66 | } 67 | } 68 | 69 | // get temperatureInt value 70 | for i = 16; i < 32; i++ { 71 | temperatureInt = temperatureInt << 1 72 | temperatureInt += bits[i] 73 | // sum 8 bits for checkSum 74 | sum8 = sum8 << 1 75 | sum8 += uint8(bits[i]) 76 | if i == 23 || i == 31 { 77 | // got 8 bits, add to sumTotal for checkSum 78 | sumTotal += sum8 79 | sum8 = 0 80 | } 81 | } 82 | // if high 16 bit is set, value is negtive 83 | // 1000000000000000 = 0x8000 84 | if (temperatureInt & 0x8000) > 0 { 85 | // flip bits 16 and lower to get negtive number for int 86 | // 1111111111111111 = 0xffff 87 | temperatureInt |= ^0xffff 88 | } 89 | 90 | // get checkSum value 91 | for i = 32; i < 40; i++ { 92 | checkSum = checkSum << 1 93 | checkSum += uint8(bits[i]) 94 | } 95 | 96 | if dht.sensorType != "dht11" { 97 | // humidity is between 0 % to 100 % 98 | if humidityInt < 0 || humidityInt > 1000 { 99 | err = fmt.Errorf("bad data - humidity: %v", humidityInt) 100 | return 101 | } 102 | // temperature between -40 C to 80 C 103 | if temperatureInt < -400 || temperatureInt > 800 { 104 | err = fmt.Errorf("bad data - temperature: %v", temperatureInt) 105 | return 106 | } 107 | // check checkSum 108 | if checkSum != sumTotal { 109 | err = fmt.Errorf("bad data - check sum fail") 110 | } 111 | 112 | humidity = float64(humidityInt) / 10.0 113 | if dht.temperatureUnit == Celsius { 114 | temperature = float64(temperatureInt) / 10.0 115 | } else { 116 | temperature = float64(temperatureInt)*9.0/50.0 + 32.0 117 | } 118 | 119 | return 120 | } 121 | 122 | // humidity is between 0 % to 100 % 123 | if humidityInt < 0 || humidityInt > 100 { 124 | err = fmt.Errorf("bad data - humidity: %v", humidityInt) 125 | return 126 | } 127 | // temperature between 0 C to 50 C 128 | if temperatureInt < 0 || temperatureInt > 50 { 129 | err = fmt.Errorf("bad data - temperature: %v", temperatureInt) 130 | return 131 | } 132 | // check checkSum 133 | if checkSum != sumTotal { 134 | err = fmt.Errorf("bad data - check sum fail") 135 | } 136 | 137 | humidity = float64(humidityInt) 138 | if dht.temperatureUnit == Celsius { 139 | temperature = float64(temperatureInt) 140 | } else { 141 | temperature = float64(temperatureInt)*9.0/5.0 + 32.0 142 | } 143 | 144 | return 145 | } 146 | 147 | // ReadRetry will call Read until there is no errors or the maxRetries is hit. 148 | // Suggest maxRetries to be set around 11. 149 | func (dht *DHT) ReadRetry(maxRetries int) (humidity float64, temperature float64, err error) { 150 | for i := 0; i < maxRetries; i++ { 151 | humidity, temperature, err = dht.Read() 152 | if err == nil { 153 | return 154 | } 155 | } 156 | return 157 | } 158 | 159 | // ReadBackground it meant to be run in the background, run as a Goroutine. 160 | // sleepDuration is how long it will try to sleep between reads. 161 | // If there is ongoing read errors there will be no notice except that the values will not be updated. 162 | // Will continue to read sensor until stop is closed. 163 | // After it has been stopped, the stopped chan will be closed. 164 | // Will panic if humidity, temperature, or stop are nil. 165 | func (dht *DHT) ReadBackground(humidity *float64, temperature *float64, sleepDuration time.Duration, stop chan struct{}, stopped chan struct{}) { 166 | var humidityTemp float64 167 | var temperatureTemp float64 168 | var err error 169 | var startTime time.Time 170 | 171 | Loop: 172 | for { 173 | startTime = time.Now() 174 | humidityTemp, temperatureTemp, err = dht.Read() 175 | if err == nil { 176 | // no read error, save result 177 | *humidity = humidityTemp 178 | *temperature = temperatureTemp 179 | // wait for sleepDuration or stop 180 | select { 181 | case <-time.After(sleepDuration - time.Since(startTime)): 182 | case <-stop: 183 | break Loop 184 | } 185 | } else { 186 | // read error, just check for stop 187 | select { 188 | case <-stop: 189 | break Loop 190 | default: 191 | } 192 | } 193 | } 194 | 195 | close(stopped) 196 | } 197 | --------------------------------------------------------------------------------