summaryrefslogtreecommitdiffstats
path: root/HvacStation/HvacStation.ino
blob: 02b2af9c41a281cfdc8903afab5448d3f1340871 (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
#include <WiFi.h>
#include <HTTPClient.h>
#include <PID_v1.h>

enum {
	SECOND = 1000,
	PERIOD = 30*SECOND,
};
enum pins { SOLENOID_PIN = 21 };
enum tunings {
	P = 2,
	I = 5,
	D = 1,
};
enum { SOLENOID_WINDOW = 5000 };

const char ssid[] = "Pixel_6504";
const char password[] = "zj3av9sjev7ed8j";
const char humidityUrl[] = "http://hvac.samanthony.xyz/humidity";
const char targetUrl[] = "http://hvac.samanthony.xyz/target_humidity";

double pidInput, pidOutput, pidSetpoint;
PID pid(&pidInput, &pidOutput, &pidSetpoint, P, I, D, DIRECT);

void
setup(void) {
	pinMode(SOLENOID_PIN, OUTPUT);

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

	pid.SetOutputLimits(0, SOLENOID_WINDOW);
	pid.SetMode(AUTOMATIC);

	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());
}

void
loop(void) {
	static unsigned long lastUpdate = 0; // Last time humidity and target were retrived from server.
	static float humidity = 0.0; // Measured humidity of the building.
	static float target = 0.0; // Target humidity.

	unsigned long now = millis();
	if (now - lastUpdate > PERIOD) {
		// Retrieve measured and target humidity from the server.
		if (get(humidityUrl, &humidity) != 0 || get(targetUrl, &target) != 0)
			Serial.println("Failed to get humidity from server.");
		lastUpdate = now;
		Serial.printf("Measured humidity: %.2f%%\n", humidity);
		Serial.printf("Target humidity: %.2f%%\n", target);
	}

	pidInput = humidity;
	pidSetpoint = target;
	pid.Compute();
	writeSolenoidPin(pidOutput);
}

// Make a GET request to the server and set *x to the float value that it responds with.
// Return non-zero on error.
int
get(const char *url, float *x) {
	if (WiFi.status() != WL_CONNECTED) {
		Serial.println("WiFi not connected.");
		return 1;
	}

	// Send request to server.
	HTTPClient http;
	Serial.printf("GET %s\n", url);
	http.begin(url);
	int responseCode = http.GET();
	Serial.printf("HTTP response code: %d\n", responseCode);
	if (responseCode != HTTP_CODE_OK) {
		http.end();
		return 1;
	}

	// Parse response.
	int status = parseFloat(http.getString().c_str(), x);
	http.end(); // Cannot be freed before parseHumidity() because response is stored in the http buffer.
	return status;
}

// Parse the value of str into *x. Returns non-zero on error.
int
parseFloat(const char *str, float *x) {
	if (sscanf(str, "%f", x) != 1) {
		Serial.printf("Failed to parse float: '%s'\n", str);
		return 1;
	}
	return 0;
}

void
writeSolenoidPin(double pidOutput) {
	static unsigned long windowStartTime = 0;

	unsigned long now = millis();
	if (now - windowStartTime > SOLENOID_WINDOW) {
		// Start new window.
		windowStartTime = now;
		float dc = pidOutput / SOLENOID_WINDOW * 100.0;
		Serial.printf("Duty cycle: %.0f%%\n", dc);
	}

	if (pidOutput > now - windowStartTime)
		digitalWrite(SOLENOID_PIN, HIGH);
	else
		digitalWrite(SOLENOID_PIN, LOW);
}