summaryrefslogtreecommitdiffstats
path: root/SensorStation/SensorStation.ino
blob: c16ae4f000256287d9f37a6e7d5b6581269ce9f8 (plain) (blame)
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
#include <WiFi.h>
#include <HTTPClient.h>
#include <DHT.h>

#define nelem(arr) (sizeof(arr) / sizeof(arr[0]))

enum pins {
	DHT_PIN = 21,

	UP_BTN = 16,
	DOWN_BTN = 17,
};
enum {
	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,
};

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.

void
setup(void) {
	pinMode(UP_BTN, INPUT);
	pinMode(DOWN_BTN, INPUT);

	Serial.begin(9600);
	while (!Serial) {}

	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 unsigned long lastSample = 0; // Last time humidity was measured.
	static float targetHumidity = defaultTarget;
	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) {
		measureHumidity();
		lastSample = now;
	}

	// Update target humidity if buttons are pressed.
	if (upButton() && targetHumidity+incTarget <= maxTarget) {
		targetHumidity += incTarget;
		targetDirty = true;
		targetDeadtimeStart = now;
		Serial.printf("Up. Target humidity: %.2f%%\n", targetHumidity);
	} else if (downButton() && targetHumidity-incTarget >= minTarget) {
		targetHumidity -= incTarget;
		targetDirty = true;
		targetDeadtimeStart = now;
		Serial.printf("Down. Target humidity: %.2f%%\n", targetHumidity);
	}

	// Send updated target humidity to server.
	if (targetDirty && now-targetDeadtimeStart > DEADTIME) {
		if (setTarget(targetHumidity) == 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.
void
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.");
}

// 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;
}