1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
|
#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define nelem(arr) (sizeof(arr) / sizeof(arr[0]))
enum pins {
DHT_PIN = 14,
UP_BTN = 13,
DOWN_BTN = 12,
OLED_SDA = 4,
OLED_SCL = 15,
OLED_RST = 16,
};
enum times {
SECOND = 1000,
PERIOD = 30*SECOND, // Humidity sample period.
DEBOUNCE = 50, // Button debounce time.
// Wait this long before updating the server when target humidity is changed.
// Avoids spamming requests when button is pressed repeatedly.
DEADTIME = 1000,
};
enum screen {
SCREEN_WIDTH = 128,
SCREEN_HEIGHT = 64,
SCREEN_I2C_ADDR = 0x3C,
TEXT_SIZE = 1,
TEXT_COLOR = WHITE,
};
const char ssid[] = "Pixel_6504";
const char password[] = "zj3av9sjev7ed8j";
const char domain[] = "hvac.samanthony.xyz";
const char humidityPath[] = "/humidity";
const char targetHumidityPath[] = "/target_humidity";
const char roomID[] = "SNbeEcs7XVWMEvjeEYgwZnp9XYjToVhh";
const float defaultTarget = 35.0; // Default target humidity.
const float minTarget = 0.0; // Minimum target humidity.
const float maxTarget = 100.0; // Maximum target humidity.
const float incTarget = 0.5; // Target humidity increment from button press.
DHT dht(DHT_PIN, DHT11); // Humidity sensor.
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);
void
setup(void) {
pinMode(UP_BTN, INPUT);
pinMode(DOWN_BTN, INPUT);
pinMode(OLED_RST, OUTPUT);
Serial.begin(9600);
while (!Serial) {}
digitalWrite(OLED_RST, LOW);
delay(20);
digitalWrite(OLED_RST, HIGH);
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C, false, false)) {
Serial.println("Failed to initialize screen");
for (;;) {}
}
delay(1000);
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("...");
display.display();
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi...");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println(" connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Initializing DHT11 humidity sensor...");
delay(1*SECOND); /* Let sensor stabilize after power-on. */
dht.begin();
Serial.println(" done.");
}
void
loop(void) {
static float humidity = 0.0; // Measured humidity
static unsigned long lastSample = 0; // Last time humidity was measured.
static float target = defaultTarget; // Target humidity.
static bool targetDirty = true; // True when target humidity is changed.
static unsigned long targetDeadtimeStart; // Don't spam requests when button pressed repeatedly.
unsigned long now = millis();
// Measure humidity.
if (now - lastSample > PERIOD) {
humidity = measureHumidity();
lastSample = now;
refreshDisplay(target, humidity);
}
// Update target humidity if buttons are pressed.
if (upButton() && target+incTarget <= maxTarget) {
target += incTarget;
targetDirty = true;
targetDeadtimeStart = now;
Serial.printf("Up. Target humidity: %.2f%%\n", target);
refreshDisplay(target, humidity);
} else if (downButton() && target-incTarget >= minTarget) {
target -= incTarget;
targetDirty = true;
targetDeadtimeStart = now;
Serial.printf("Down. Target humidity: %.2f%%\n", target);
refreshDisplay(target, humidity);
}
// Send updated target humidity to server.
if (targetDirty && now-targetDeadtimeStart > DEADTIME) {
if (setTarget(target) == 0) {
targetDirty = false; // Success.
} else {
Serial.println("Failed to send target humidity to server.");
targetDeadtimeStart = now; // Delay retry.
}
}
}
// Measure the humidity and send it to the server.
float
measureHumidity(void) {
// Measure humidity.
float humidity = dht.readHumidity();
Serial.printf("Humidity: %.2f %% RH\n", humidity);
// Send measured humidity to server.
const char *url = humidityUrl(humidity);
if (post(url) != 0)
Serial.println("Failed to send humidity to server.");
return humidity;
}
void
refreshDisplay(float target, float humidity) {
display.clearDisplay();
display.setCursor(0, 0);
display.printf("- Humidity -\n\n%8s: %5.1f%%\n\n%8s: %5.1f%%\n",
"Target", target, "Measured", humidity);
display.display();
}
// Set the target humidity on the server. Return non-zero on error.
int
setTarget(float target) {
const char *url = targetUrl(target);
return post(url);
}
// Format the humidity URL string.
char *
humidityUrl(float humidity) {
static char query[256];
int n;
n = snprintf(query, nelem(query), "room=%s&humidity=%.2f", roomID, humidity);
if (n >= nelem(query))
Serial.println("Humidity query string buffer overflow; truncating.");
return url(domain, humidityPath, query);
}
// Format the target humidity URL string.
char *
targetUrl(float target) {
static char query[256];
int n;
n = snprintf(query, nelem(query), "%.2f", target);
if (n >= nelem(query))
Serial.println("Target query string buffer overflow; truncating.");
return url(domain, targetHumidityPath, query);
}
// Make a POST request to the server.
int
post(const char *url) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi not connected.");
return 1;
}
WiFiClient client;
HTTPClient http;
Serial.printf("POST %s...\n", url);
http.begin(client, url);
int responseCode = http.POST("");
http.end();
Serial.printf("HTTP response code: %d\n", responseCode);
if (responseCode != HTTP_CODE_OK)
return 1;
return 0;
}
// Format the url string. Query should not include the '?'.
char *
url(const char *domain, const char *path, const char *query) {
static char buf[512];
int n;
n = snprintf(buf, nelem(buf), "http://%s%s?%s", domain, path, query);
if (n >= nelem(buf))
Serial.println("URL string buffer overflow; truncating.");
return buf;
}
// Return true if the UP button was pressed.
bool
upButton(void) {
static bool state = LOW;
static unsigned long lastEvent = 0;
return buttonPressed(UP_BTN, &state, &lastEvent);
}
// Return true if the DOWN button was pressed.
bool
downButton(void) {
static bool state = LOW;
static unsigned long lastEvent = 0;
return buttonPressed(DOWN_BTN, &state, &lastEvent);
}
// Return true if the button connected to the specified pin was pressed.
bool
buttonPressed(int pin, bool *lastState, unsigned long *lastEvent) {
unsigned long now = millis();
if (digitalRead(pin)) {
if (*lastState == LOW && now-*lastEvent > DEBOUNCE) {
// Rising event.
*lastState = HIGH;
*lastEvent = now;
return true;
}
} else if (now-*lastEvent > DEBOUNCE) {
// Falling event.
*lastState = LOW;
*lastEvent = now;
}
return false;
}
|