-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathsous-vide.ino
297 lines (248 loc) · 9.94 KB
/
sous-vide.ino
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
// PID Library
#include "PID.h"
//Includes for the DS1820 Temp Sensor
#include "ds1820.h"
#include "OneWire.h"
// ***********************************
// Pin Definitions
// ***********************************
#define ONE_WIRE_PIN D6 //Pin for DS1820 sensor
#define RELAY_PIN D0 //Pin to control relay
// ***********************************
// Defining Xively ID's
// ***********************************
#define FEED_ID "FAKE_SHORT_NO"
#define XIVELY_API_KEY "FAKE_LONG_NO"
// ***********************************
// Sensor Vairables and Constants
// plugged into D6
// ***********************************
static OneWire oneWire(ONE_WIRE_PIN); // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
static DS1820Sensor sensor;
// ***********************************
// Global Definitions
// ***********************************
bool PIDActive = false; // Is PID Controller Active?
bool RelayUsable = false; // Allows the relay to be overridden so can completely shut everything down! #SAFETY
double cTemp =0 ; // Stores the current temp. Refactor cTemp into Input? Seems completely pointless having exactly the same data.
TCPClient client; //Used for sending posting details, temp etc.
unsigned long LastSyncTime = 0; //Keeps count of the last time the temp was sent to the cloud (syncs every 30 secs).
// ***********************************
// PID Variables and constants
// ***********************************
double Setpoint = 0; //Target temperature - Set to zero so GetSetPt can be called from the cloud before it is set.
double Input = 0; //Current Temperature
double Output; //How much needs to be output to make Input match SetPoint. Effectively this decides if the relay is on or off.
// PID Tuning parameters
double Kp = 0.1; //http://www.over-engineered.com/projects/sous-vide-pid-controller/
double Ki = 150; //
double Kd = 0.45; //
// 10 second Time Proportional Output window. Checks what the PID should do every 10 seconds.
int WindowSize = 10000;
unsigned long windowStartTime;
unsigned long now;
//Specify the links and initial tuning parameters
//Input = Current Temp of the SousVide machine
//Output = On or off the relay. Is actually a double and if under 1000 then relay turned off.
//Setpoint = Target Temp
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
void setup() {
// ***********************************
// Spark Functions
// ***********************************
Spark.function("SetSetPt", SetSetPoint);
Spark.function("TogglePID", TogglePID);
Spark.function("SetTunings", SetTunings);
// ***********************************
// Spark Variables
// ***********************************
Spark.variable("GetSetPt", &Setpoint, DOUBLE);
Spark.variable("GetCTemp", &cTemp, DOUBLE);
Spark.variable("GetRelay", &RelayUsable, INT);
Spark.variable("GetPIDAct", &PIDActive, INT);
Spark.variable("GetKp", &Kp, DOUBLE); //This could probably be made into one variable returning Kp, Ki, Kd as a one nice big string
Spark.variable("GetKi", &Ki, DOUBLE);
Spark.variable("GetKd", &Kd, DOUBLE);
// ***********************************
// Relay setup
// ***********************************
pinMode(RELAY_PIN, OUTPUT);
if(RelayUsable)digitalWrite(RELAY_PIN, LOW); //Ensure the relay is turned off to begin with.
delay(1000);
// ***********************************
// PID Setup
// ***********************************
windowStartTime = millis();
myPID.SetTunings(Kp, Ki, Kd);
myPID.SetSampleTime(1000); //95% certain I don't need this line. Don't want to check that theory though as cooker is con.
myPID.SetOutputLimits(0, WindowSize);
myPID.SetMode(AUTOMATIC);
}
void loop()
{
//REMEMBER - ALL DigitalWrites to relay MUST go through RelayActive first.
GetCurrentTemp(); //Allows the temperature to be read, even when PID/Relay isn't enabled. Doesn't "smell" right. I would prefer it to be called by a Spark Function otherwise it is updating every second when no-one really cares.
if(PIDActive) //If PID has been turned on then allow do work and to control relay.
{
DoWork();
}
delay(1000);
if (millis()-LastSyncTime>1000*30) //Sync the current temp with Xively.
{
xivelyTemp();
LastSyncTime = millis();
}
}
void xivelyTemp() {
if (client.connect("api.xively.com", 8081))
{
// Connection succesful, update datastreams
client.print("{");
client.print(" \"method\" : \"put\",");
client.print(" \"resource\" : \"/feeds/");
client.print(FEED_ID);
client.print("\",");
client.print(" \"params\" : {},");
client.print(" \"headers\" : {\"X-ApiKey\":\"");
client.print(XIVELY_API_KEY);
client.print("\"},");
client.print(" \"body\" :");
client.print(" {");
client.print(" \"version\" : \"1.0.0\",");
client.print(" \"datastreams\" : [");
client.print(" {");
client.print(" \"id\" : \"CurrentTemp\","); //Feed or channel to be updated by put command.
client.print(" \"current_value\" : \"");
client.print(cTemp); //Value to be put on the Xively server
client.print("\"");
client.print(" }");
client.print(" ]");
client.print(" },");
client.print(" \"token\" : \"0x123abc\"");
client.print("}");
client.println();
}
else
{
}
client.flush();
delay(2000); //Stop connection being closed before everything has been sent. Stupid crappy router.
client.stop();
}
void DoWork()
{
Input = cTemp;
delay(1000);
myPID.Compute();
now = millis();
if(now - windowStartTime>WindowSize)
{
windowStartTime += WindowSize;
}
if(Output > now - windowStartTime)
{
if(RelayUsable)digitalWrite(RELAY_PIN,HIGH);
}
else
{
if(RelayUsable)digitalWrite(RELAY_PIN,LOW);
}
}
//Gets the current temperature of the probe. Again doesn't really smell right, not a get. More of an update the sensor, assign that to cTemp and then return cTemp. Not as catchy though. Refactor Input instead of Ctemp
double GetCurrentTemp()
{
updateSensor();
cTemp = sensor.GetTemp()/1000000.0;
return cTemp;
}
int SetTunings(String command) //Aquired from https://github.com/plan44/messagetorch/blob/master/messagetorch.cpp
{ //Would quite like for JSON to be passed in and read, however seems a bit OTT. For another day perhaps.
int p = 0;
while (p<(int)command.length())
{
int i = command.indexOf(',',p);
if (i<0) i = command.length();
int j = command.indexOf('=',p);
if (j<0) break;
String key = command.substring(p,j);
String value = command.substring(j+1,i);
double val = atof(value.c_str());
if(key=="Kp")
Kp = val;
if(key=="Ki")
Ki = val;
if(key=="Kd")
Kd = val;
p = i + 1;
}
return (int)(Kp + Ki + Kd);
}
int SetSetPoint(String args) //Sets the target temperature(in Degrees) also used as the Setpoint for the PID. Used for Spark.Function "SetSetPt";
{
Setpoint = atof(args.c_str()); //Error checking - if Input = Args and Active = true then return 1 else return 2? Worthwhile?
TogglePID("ON");
if(RelayUsable) //Doesn't do anything, TogglePID makes relay usable anyway. May be removed - Return TogglePID("ON"); instead.
{
return 1;
}
else
{
return 2;
}
}
//Allows PID to be turned on or off
int TogglePID(String args) //Can't say I particularly like using "ON" or "OFF", string comparison feels flaky.
{
if(args == "ON")
{
PIDActive = true;
RelayUsable = true;
return 1;
}
else if(args == "OFF")
{
PIDActive = false;
if(RelayUsable)digitalWrite(RELAY_PIN,LOW);
RelayUsable = false;
return 2;
}
else
{
return -1;
}
}
int updateSensor() //I wish I could remember where I stole this from.
{
uint8_t addr[8];
unsigned long now = millis();
// Are we already working on a sensor? service it, possibly writting value,
if (sensor.Initialized() || sensor.Busy())
{
if (sensor.Update(now))
{
uint8_t *addr=sensor.m_addr;
//sensor.Reset();
}
else if (sensor.Busy())
{
// More cycles needed on this sensor
return 1;
}
else
{
// finished or not started
}
}
// First time, or finished with last sensor; clean up, and look more more devices.
int more_search = oneWire.search(addr);
if (!more_search)
{
// Bus exhausted; start over
oneWire.reset_search();
return 0;
}
// New sensor. Initialize and start work.
sensor.Initialize(&oneWire, addr);
sensor.Update(now);
return 1;
}