diff options
-rw-r--r-- | dht11/dht11.go | 218 | ||||
-rw-r--r-- | main.go | 43 |
2 files changed, 261 insertions, 0 deletions
diff --git a/dht11/dht11.go b/dht11/dht11.go new file mode 100644 index 0000000..4d54aff --- /dev/null +++ b/dht11/dht11.go | |||
@@ -0,0 +1,218 @@ | |||
1 | package dht11 | ||
2 | |||
3 | import ( | ||
4 | "errors" | ||
5 | "log" | ||
6 | "time" | ||
7 | |||
8 | "github.com/stianeikeland/go-rpio" | ||
9 | ) | ||
10 | |||
11 | type tempState int | ||
12 | |||
13 | const ( | ||
14 | initPullDown tempState = iota | ||
15 | initPullUp | ||
16 | dataFirstPullDown | ||
17 | dataPullUp | ||
18 | dataPullDown | ||
19 | ) | ||
20 | |||
21 | type SensorData struct { | ||
22 | TempC int | ||
23 | TempF float32 | ||
24 | Humidity int | ||
25 | } | ||
26 | |||
27 | type Device struct { | ||
28 | StartLowTime time.Duration | ||
29 | LowTime time.Duration | ||
30 | HighTime time.Duration | ||
31 | ReadDelay time.Duration | ||
32 | } | ||
33 | |||
34 | var RaspberryPi Device | ||
35 | |||
36 | func init() { | ||
37 | // These were determined experimentally using a logic analyser a Raspberry | ||
38 | // Pi 3 B+. They may vary for other device types and processor speeds. | ||
39 | RaspberryPi = Device{ | ||
40 | StartLowTime: 4 * time.Microsecond, | ||
41 | LowTime: 15 * time.Millisecond, | ||
42 | HighTime: 50 * time.Millisecond, | ||
43 | ReadDelay: 1 * time.Microsecond, | ||
44 | } | ||
45 | } | ||
46 | |||
47 | type DHT11 struct { | ||
48 | Device *Device | ||
49 | Pin *rpio.Pin | ||
50 | } | ||
51 | |||
52 | func NewRaspberryPiDHT11(pin *rpio.Pin) *DHT11 { | ||
53 | return &DHT11{ | ||
54 | Device: &RaspberryPi, | ||
55 | Pin: pin, | ||
56 | } | ||
57 | } | ||
58 | |||
59 | func (d *DHT11) sendDataRequest() { | ||
60 | d.Pin.Output() | ||
61 | |||
62 | d.Pin.Low() | ||
63 | time.Sleep(d.Device.StartLowTime) | ||
64 | d.Pin.High() | ||
65 | time.Sleep(d.Device.HighTime) | ||
66 | d.Pin.Low() | ||
67 | time.Sleep(d.Device.LowTime) | ||
68 | |||
69 | d.Pin.Input() | ||
70 | d.Pin.PullUp() | ||
71 | } | ||
72 | |||
73 | func (d *DHT11) gatherRawData() []rpio.State { | ||
74 | var i int | ||
75 | var last rpio.State | ||
76 | data := make([]rpio.State, 0) | ||
77 | |||
78 | for i = 0; i < 500; i++ { | ||
79 | current := d.Pin.Read() | ||
80 | time.Sleep(d.Device.ReadDelay) | ||
81 | data = append(data, current) | ||
82 | if last != current { | ||
83 | i = 0 | ||
84 | last = current | ||
85 | } | ||
86 | } | ||
87 | |||
88 | return data | ||
89 | } | ||
90 | |||
91 | func (d *DHT11) parseSignals(input []rpio.State) ([]int, error) { | ||
92 | state := initPullDown | ||
93 | lengths := make([]int, 0, 40) | ||
94 | currentLength := 0 | ||
95 | |||
96 | for _, current := range input { | ||
97 | currentLength++ | ||
98 | |||
99 | switch state { | ||
100 | case initPullDown: | ||
101 | if current == rpio.Low { | ||
102 | state = initPullUp | ||
103 | } | ||
104 | case initPullUp: | ||
105 | if current == rpio.High { | ||
106 | state = dataFirstPullDown | ||
107 | } | ||
108 | case dataFirstPullDown: | ||
109 | if current == rpio.Low { | ||
110 | state = dataPullUp | ||
111 | } | ||
112 | case dataPullUp: | ||
113 | if current == rpio.High { | ||
114 | currentLength = 0 | ||
115 | state = dataPullDown | ||
116 | } | ||
117 | case dataPullDown: | ||
118 | if current == rpio.Low { | ||
119 | lengths = append(lengths, currentLength) | ||
120 | state = dataPullUp | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | |||
125 | if len(lengths) != 40 { | ||
126 | return nil, errors.New("not enough data returned") | ||
127 | } | ||
128 | |||
129 | return lengths, nil | ||
130 | } | ||
131 | |||
132 | func (d *DHT11) signalsToBytes(lengths []int) ([]int, error) { | ||
133 | bytes := make([]int, 0, 5) | ||
134 | |||
135 | longestUp, shortestUp := 0, 1000 | ||
136 | for _, length := range lengths { | ||
137 | if length < shortestUp { | ||
138 | shortestUp = length | ||
139 | } | ||
140 | |||
141 | if length > longestUp { | ||
142 | longestUp = length | ||
143 | } | ||
144 | } | ||
145 | |||
146 | halfway := shortestUp + (longestUp-shortestUp)/2 | ||
147 | |||
148 | bits := make([]bool, 0, len(lengths)) | ||
149 | for _, length := range lengths { | ||
150 | bit := false | ||
151 | if length > halfway { | ||
152 | bit = true | ||
153 | } | ||
154 | bits = append(bits, bit) | ||
155 | } | ||
156 | |||
157 | currentByte := 0 | ||
158 | for i, bit := range bits { | ||
159 | currentByte <<= 1 | ||
160 | if bit { | ||
161 | currentByte |= 1 | ||
162 | } else { | ||
163 | currentByte |= 0 | ||
164 | } | ||
165 | |||
166 | if (i+1)%8 == 0 { | ||
167 | bytes = append(bytes, currentByte) | ||
168 | currentByte = 0 | ||
169 | } | ||
170 | } | ||
171 | |||
172 | checksum := (bytes[0] + bytes[1] + bytes[2] + bytes[3]) & 255 | ||
173 | if bytes[4] != checksum { | ||
174 | return nil, errors.New("CRC error") | ||
175 | } | ||
176 | |||
177 | return bytes, nil | ||
178 | } | ||
179 | |||
180 | func (d *DHT11) GetSensorData() (*SensorData, error) { | ||
181 | d.sendDataRequest() | ||
182 | |||
183 | rawData := d.gatherRawData() | ||
184 | signals, err := d.parseSignals(rawData) | ||
185 | if err != nil { | ||
186 | return nil, err | ||
187 | } | ||
188 | |||
189 | dataBytes, err := d.signalsToBytes(signals) | ||
190 | if err != nil { | ||
191 | return nil, err | ||
192 | } | ||
193 | |||
194 | return &SensorData{ | ||
195 | TempC: dataBytes[2], | ||
196 | TempF: celsiusToFarenheit(dataBytes[2]), | ||
197 | Humidity: dataBytes[0], | ||
198 | }, nil | ||
199 | } | ||
200 | |||
201 | func (d *DHT11) GetSensorDataWithRetry(rc int) (data *SensorData, err error) { | ||
202 | for i := 0; i < rc; i++ { | ||
203 | data, err = d.GetSensorData() | ||
204 | if err == nil { | ||
205 | return | ||
206 | } else { | ||
207 | log.Printf("error") | ||
208 | time.Sleep(1 * time.Second) | ||
209 | continue | ||
210 | } | ||
211 | } | ||
212 | |||
213 | return | ||
214 | } | ||
215 | |||
216 | func celsiusToFarenheit(c int) float32 { | ||
217 | return float32(c)*1.8 + 32 | ||
218 | } | ||
@@ -0,0 +1,43 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "log" | ||
5 | "time" | ||
6 | |||
7 | "dht11" | ||
8 | |||
9 | "github.com/stianeikeland/go-rpio" | ||
10 | ) | ||
11 | |||
12 | const ( | ||
13 | RELAY_PIN = 27 | ||
14 | TEMP_PIN = 17 | ||
15 | ) | ||
16 | |||
17 | func toggleRelay(pin *rpio.Pin) { | ||
18 | pin.Output() | ||
19 | pin.PullDown() | ||
20 | |||
21 | pin.High() | ||
22 | time.Sleep(10 * time.Second) | ||
23 | pin.Low() | ||
24 | } | ||
25 | |||
26 | func main() { | ||
27 | err := rpio.Open() | ||
28 | if err != nil { | ||
29 | log.Fatal(err) | ||
30 | } | ||
31 | defer rpio.Close() | ||
32 | |||
33 | tempPin := rpio.Pin(TEMP_PIN) | ||
34 | tempSensor := dht11.NewRaspberryPiDHT11(&tempPin) | ||
35 | |||
36 | if temp, err := tempSensor.GetSensorDataWithRetry(5); err == nil { | ||
37 | log.Printf("temp: %d\n", temp.TempC) | ||
38 | log.Printf("temp: %f\n", temp.TempF) | ||
39 | log.Printf("humidity: %d\n", temp.Humidity) | ||
40 | } else { | ||
41 | log.Printf("Failed to read sensor") | ||
42 | } | ||
43 | } | ||