· 6 years ago · Apr 01, 2020, 09:20 AM
1// To Do:
2// - maybe make addition of fertilizer dependent on season
3#include <SoftwareSerial.h>
4#include "RTClib.h" // For Real Time Clock
5#include <LightwaveRF.h> // To control Lightwave RF remote mains with 433 Hz Transmitter module
6#include <Pixy2.h> // For Pixy2 camera
7#include <stdio.h> // For Pixy2 camera
8#include "ThingSpeak.h"
9#include "WiFiEsp.h"
10#include "secrets.h"
11#include "time.h"
12#include <TimeLib.h>
13#include <math.h>
14#include "WiFiEspUdp.h"
15#include <Timezone.h>
16
17/*
18Remember the following (https://github.com/bportaluri/WiFiEsp/blob/master/examples/UdpNTPClient/UdpNTPClient.ino)
19 NOTE: The serial buffer size must be larger than 36 + packet size
20 In this example we use an UDP packet of 48 bytes so the buffer must be
21 at least 36+48=84 bytes that exceeds the default buffer size (64).
22 You must modify the serial buffer size to 128
23 For HardwareSerial modify _SS_MAX_RX_BUFF in
24 Arduino\hardware\arduino\avr\cores\arduino\SoftwareSerial.h
25 For SoftwareSerial modify _SS_MAX_RX_BUFF in
26 Arduino\hardware\arduino\avr\libraries\SoftwareSerial\SoftwareSerial.h
27*/
28
29// Remember that Arduino ESP8266 might interfere with e.g. laptop connecting to Wifi; ESP8266 uses 2.4 GHz -> solution: have laptop prefer 5 GHz (adapter settings -> properties -> configure -> Advanced -> Preferred Band)
30
31char timeServer[] = "time.nist.gov"; // NTP server
32unsigned int localPort = 2390; // local port to listen for UDP packets
33
34const int NTP_PACKET_SIZE = 48; // NTP timestamp is in the first 48 bytes of the message
35const int UDP_TIMEOUT = 2000; // timeout in miliseconds to wait for an UDP packet to arrive
36
37byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets
38
39WiFiEspUDP Udp; // A UDP instance to let us send and receive packets over UDP
40
41// How to create an e-mail notification: https://www.elecfreaks.com/store/blog/how-to-send-temperature-threshold-value-alarm-email-via-ifttt.html
42
43// For debugging purposes
44boolean VerboseOut = false;
45
46// For 433 Hz Transmitter module
47byte on[] = {0xf6,0xf6,0xf6,0xee,0x6f,0xeb,0xbe,0xed,0xb7,0x7b};
48byte off[] = {0xf6,0xf6,0xf6,0xf6,0x6f,0xeb,0xbe,0xed,0xb7,0x7b};
49
50// Real time clock
51RTC_DS3231 rtc;
52
53// Main Pixy object
54Pixy2 pixy;
55
56String answer[2]; // Array holding data received from Titrator
57
58char ssid[] = SECRET_SSID; // network SSID (name)
59char pass[] = SECRET_PASS; // network password
60int keyIndex = 0; // network key Index number (needed only for WEP)
61WiFiEspClient client;
62
63unsigned long myChannelNumber = SECRET_CH_ID; // Thingspeak channel ID
64const char * myWriteAPIKey = SECRET_WRITE_APIKEY; // Thingspeak channel API key
65
66// SETTINGS
67
68// General settings
69int MonitoringDelay = 1; // Time interval between recordings of sensor data, in minutes
70
71// Camera-related settings
72String CameraStart = "09:00"; // Start of time frame during which to acquire a picture
73String CameraEnd = "17:00"; // End of time frame during which to acquire a picture
74int LightThreshold = 450; // Only acquire picture if light level is beyond this threshold (raw sensor value); default: 450
75const int LightAveragingWindow = 15; // Number of most recent datapoints to be taken for calculation of averaged light level and standard deviation
76
77// Watering-related settings
78String WateringStart = "09:00"; // Start of time frame during which to water
79String WateringEnd = "17:00"; // End of time frame during which to water
80int WateringVolume = 100; // Total water volume used per watering in mL
81int WaterSyringeLimit = 20; // Limiting pickup volume for water syringe to avoid stalling
82int WaterSyringeVolume = 25; // Volume of left syringe used for water in mL
83int FertilizerSyringeVolume = 100; // Volume of right syringe used for fertilizer, in microliters
84int MinimumWateringInterval = 60*60*24; // Minimum time in seconds that must have passed since last watering for next watering to happen
85int MaxWaterLevelDistance = 45; // Distance in mm at full water reservoir
86int MinWaterLevelDistance = 230; // Distance in mm at empty water reservoir
87int WaterReservoirVolume = 1500; // Maximum water reservoir volume in mL
88int WaterLevelOutputAccuracy = 100; // Accuracy limit in mL for reported water level; should be chosen to be same as watering volume
89boolean AddFertilizer = false; // Decides whether fertilizer is added upon watering
90float FertilizerToWaterRatio = 0.003; // Volumetric ratio of fertilizer added to water
91
92// Moisture-related settings
93// float moistureCalWet = 200.0; // Raw moisture sensor readout approx. corresponding to maximum moisture (100%, water)
94// float moistureCalDry = 400.0; // Raw moisture sensor readout approx. corresponding to minimum moisture (0%, dry sensor)
95float MoistureThreshold = 300.0; // Minimum (raw sensor) moisture level at which ficus is watered. (Very low value corresponds to high moisture, e.g. ~200 for pure water)
96float MoistureSDThreshold = 1.00; // Minimum moisture level standard deviation. I.e., ficus is only watered when moisture readings have been stable over a certain time period.
97const int MoistureAveragingWindow = 60; // Number of most recent datapoints to be taken for calculation of averaged moisture level and standard deviation
98
99// VARIABLES INITIALIZATION
100
101int ThingSpeakResponse;
102String statusadd;
103String myStatus = "";
104
105// Timing-related variables
106DateTime lastwatering;
107time_t wateringtimedif;
108time_t UTCtime;
109time_t nowUTC;
110time_t nowLocal;
111DateTime nowLocalDateTime;
112boolean wateredonce = false;
113boolean timeinit = false;
114boolean waiting = false;
115int elapseddays;
116int elapsedhours;
117int elapsedminutes;
118int elapsedseconds;
119int oldminutes;
120int newminutes;
121int TimeSinceLastDataCollection;
122unsigned long epoch;
123// Timezone rules for United Kingdom (London, Belfast)
124TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; // British Summer Time
125TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; // Standard Time
126Timezone UK(BST, GMT);
127
128// Light-related variables
129int LightPin = 0; // Pin for photocell
130int LightRawReadout;
131int CurrentLightLevel;
132float AverageLightLevel;
133int StoredLightValues[LightAveragingWindow];
134
135// Moisture-related variables
136int MoisturePin = 1; // Pin for moisture sensor
137int MoistureRawReadout;
138float CurrentMoistureLevel;
139float AverageMoistureLevel;
140int StoredMoistureValues[MoistureAveragingWindow];
141float MoistureSD;
142
143// Water level-related variables
144float WaterLevel;
145int trigPin = 4; // Trigger of HC-SR04 ultrasonic sensor
146int echoPin = 5; // Echo of HC-SR04 ultrasonic sensor
147
148// Variables for Pixy2 camera and related calculations
149boolean pixelsacquired = false;
150boolean cameraupdate = false;
151long GreenPixelsL = 0;
152long GreenPixelsLpercentage = 0.0;
153long YellowPixelsL = 0;
154long YellowPixelsLpercentage = 0.0;
155long GreenPixelsV = 0;
156long GreenPixelsVpercentage = 0.0;
157long YellowPixelsV = 0;
158long YellowPixelsVpercentage = 0.0;
159long GreenPixelsI = 0;
160long GreenPixelsIpercentage = 0.0;
161long YellowPixelsI = 0;
162long YellowPixelsIpercentage = 0.0;
163long currentprogress = 0;
164long progressstep = 10;
165long progress = progressstep;
166float H; // Hue
167float S; // Saturation
168float V; // Value
169
170void setup(){
171 // Initialize serial for output
172 Serial.begin(9600);
173
174 //timestampDebug(); // DEBUG
175
176 // Initialize 433 Hz Transmitter module
177 lw_setup();
178
179 // Initialize DS3231 Real Time Clock
180 if (! rtc.begin()) {
181 Serial.println(F("Couldn't find RTC"));
182 while (1); // can't go further
183 }
184
185 // Initialize Pixy2 camera
186 pixy.init();
187 // Change to Pixy2 'video' program
188 pixy.changeProg("video");
189
190 //Define inputs and outputs for HC-SR04 ultrasonic sensor
191 pinMode(trigPin, OUTPUT);
192 pinMode(echoPin, INPUT);
193
194 // Initialize serial for ESP module
195 Serial1.begin(115200);
196 // Initialize ESP module
197 WiFi.init(&Serial1);
198
199 // Check for the presence of the shield
200 if (WiFi.status() == WL_NO_SHIELD) {
201 Serial.println(F("WiFi shield not present"));
202 while (true);
203 }
204 // Initialize ThingSpeak
205 ThingSpeak.begin(client);
206
207 // Initialize serial for Titrator
208 Serial2.begin(9600, SERIAL_7O1);
209
210 // Turn off power just in case
211 PowerOff();
212 // Turn off Pixy2 LEDs just in case
213 if (VerboseOut == true) Serial.println(F("Turning off Pixy2 lamp..."));
214 pixy.setLamp(0, 0);
215
216 // Connect or reconnect to WiFi
217 if(WiFi.status() != WL_CONNECTED){
218 Serial.print(F("Attempting to connect to SSID: "));
219 Serial.println(SECRET_SSID);
220 while(WiFi.status() != WL_CONNECTED){
221 WiFi.begin(ssid, pass); // Connect to WPA/WPA2 network. Change this line if using open or WEP network
222 delay(5000);
223 }
224 }
225
226// Get NTP time
227Udp.begin(localPort);
228while (timeinit == false) {
229 Serial.print(F("Trying to get NTP time..."));
230 sendNTPpacket(timeServer); // send an NTP packet to a time server
231
232 // Wait for a reply for UDP_TIMEOUT miliseconds
233 unsigned long startMs = millis();
234 while (!Udp.available() && (millis() - startMs) < UDP_TIMEOUT) {}
235
236 if (Udp.parsePacket()) { // Packet has been received
237 if (VerboseOut == true) Serial.println(F("UDP packet received..."));
238 Udp.read(packetBuffer, NTP_PACKET_SIZE); // Read data from packet into buffer
239
240 // Timestamp starts at byte 40 of the received packet and is four bytes, or two words, long. First, esxtract the two words:
241 unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
242 unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
243 // Combine the four bytes (two words) into a long integer, this is NTP time (seconds since Jan 1 1900):
244 unsigned long secsSince1900 = highWord << 16 | lowWord;
245
246 // Convert NTP time into everyday time:
247 // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
248 const unsigned long seventyYears = 2208988800UL;
249 // Subtract seventy years from NTP time to get Unix time:
250 epoch = secsSince1900 - seventyYears;
251 // Account for daylight saving time
252
253
254 // Print the hour, minute and second:
255 Serial.print(F(" - Succsess. The UTC time is ")); // UTC is the time at Greenwich Meridian (GMT)
256 Serial.print((epoch % 86400L) / 3600); // Print the hour (86400 equals secs per day)
257 Serial.print(':');
258 if (((epoch % 3600) / 60) < 10) {
259 // In the first 10 minutes of each hour, we'll want a leading '0'
260 Serial.print('0');
261 }
262 Serial.print((epoch % 3600) / 60); // Print the minute (3600 equals secs per minute)
263 Serial.print(':');
264 if ((epoch % 60) < 10) {
265 // In the first 10 seconds of each minute, we'll want a leading '0'
266 Serial.print('0');
267 }
268 Serial.println(epoch % 60); // Print the second
269 timeinit = true;
270 } else {
271 Serial.println(F(" - Failed."));
272 }
273
274 delay(10000); // Wait ten seconds before asking for the time again
275}
276 UTCtime = (time_t) epoch;
277 Serial.print(F("UTCtime + 11: "));
278 Serial.println(String(UTCtime + 11));
279
280 // Set RTC time
281 if (VerboseOut == true) Serial.println(F("Setting RTC time..."));
282 // rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // Set real time clock based on compilation time - will not be accurate if Arduino is reset upon unplugging of USB!
283 rtc.adjust(UTCtime + 11); // Set real time clock based on NTP time; Add a few seconds to account for delay
284
285 nowUTC = rtc.now().unixtime(); // Get current unix timestamp from rtc
286 Serial.print("nowUTC: ");
287 Serial.println(nowUTC);
288 nowLocal = UK.toLocal(nowUTC); // Convert time according to day light saving time rule
289 Serial.print("nowLocal: ");
290 Serial.println(nowLocal);
291 nowLocalDateTime = nowLocal;
292 Serial.print(F("datetimestring: "));
293 Serial.println(datetimestring(nowLocalDateTime));
294 oldminutes = nowLocalDateTime.minute();
295 lastwatering = nowLocalDateTime;
296}
297
298void loop(){
299
300 nowUTC = rtc.now().unixtime(); // Get current unix timestamp from rtc
301 nowLocal = UK.toLocal(nowUTC); // Convert time according to day light saving time rule
302 DateTime now = nowLocal;
303
304 if (waiting == false) {
305 Serial.print(F("Waiting"));
306 }
307
308 if (now.minute() >= oldminutes) {
309 TimeSinceLastDataCollection = now.minute() - oldminutes;
310 } else {
311 TimeSinceLastDataCollection = now.minute() - oldminutes + 60;
312 }
313
314 if (TimeSinceLastDataCollection >= MonitoringDelay) {
315
316 waiting = false;
317 Serial.println("");
318 Serial.println(datetimestring(now));
319 Serial.println(datetimestring(nowLocal));
320
321 // Connect (or reconnect) to WiFi
322 if(WiFi.status() != WL_CONNECTED){
323 Serial.print(F("Attempting to connect to SSID: "));
324 Serial.println(SECRET_SSID);
325 while(WiFi.status() != WL_CONNECTED){
326 WiFi.begin(ssid, pass); // Connect to WPA/WPA2 network. Change this line if using open or WEP network
327 delay(5000);
328 }
329 Serial.println(F("Connected."));
330 }
331
332 // Get sensor values
333 if (VerboseOut == true) Serial.println(F("Getting sensor values..."));
334 LightRawReadout = analogRead(LightPin);
335 MoistureRawReadout = analogRead(MoisturePin);
336 WaterLevel = getWaterLevel();
337
338 // Calculate moisture level in percent
339 // CurrentMoistureLevel = round(100.0 * (1.0 - (float(MoistureRawReadout) - moistureCalWet) / (moistureCalDry - moistureCalWet)));
340
341 // Update stored values and get new averages and standard deviation
342 UpdatePreviousValues(StoredMoistureValues,MoistureAveragingWindow, MoistureRawReadout);
343 UpdatePreviousValues(StoredLightValues,LightAveragingWindow, LightRawReadout);
344 CurrentLightLevel = LightRawReadout;
345 CurrentMoistureLevel = MoistureRawReadout;
346 AverageMoistureLevel = GetValuesAverage(StoredMoistureValues,MoistureAveragingWindow);
347 AverageLightLevel = GetValuesAverage(StoredLightValues,LightAveragingWindow);
348 MoistureSD = GetValuesSD(StoredMoistureValues,MoistureAveragingWindow);
349 MoistureSD = MoistureSD / AverageMoistureLevel * 100.0; // Convert SD to percentage of average
350
351 // Report sensor values in Serial Monitor
352 Serial.print(F("Current light level: "));
353 Serial.println(CurrentLightLevel);
354 Serial.print(F("Average light level over the last "));
355 Serial.print(GetValuesAvailable(StoredLightValues, LightAveragingWindow));
356 Serial.print(F(" points: "));
357 Serial.println(AverageLightLevel);
358 // Serial.println("Moisture level: " + String(CurrentMoistureLevel) + "% (Raw readout: " + MoistureRawReadout + ")"); // Use if working with percent.
359 Serial.print(F("Current moisture level: "));
360 Serial.println(CurrentMoistureLevel);
361 Serial.print(F("Average moisture level over the last "));
362 Serial.print(GetValuesAvailable(StoredMoistureValues, MoistureAveragingWindow));
363 Serial.print(F(" points: "));
364 Serial.println(AverageMoistureLevel);
365 Serial.print(F("Moisture level SD over the last "));
366 Serial.print(GetValuesAvailable(StoredMoistureValues, MoistureAveragingWindow));
367 Serial.print(F(" points: "));
368 Serial.println(MoistureSD);
369 Serial.print("Water level: ");
370 Serial.print(String(int(WaterLevel)));
371 Serial.println(F(" mL"));
372
373 // Decide if picture should be acquired
374 if ((now.hour() > CameraStart.substring(0, 2).toInt() || (now.hour() == CameraStart.substring(0, 2).toInt() && now.minute() > CameraStart.substring(CameraStart.length() - 2, CameraStart.length()).toInt())) && (now.hour() < CameraEnd.substring(0, 2).toInt() || (now.hour() == CameraEnd.substring(0, 2).toInt() && now.minute() < CameraEnd.substring(CameraEnd.length() - 2, CameraEnd.length()).toInt()))) {
375 if (pixelsacquired == false && CurrentLightLevel > LightThreshold && AverageLightLevel > LightThreshold) {
376 getGreenPixels(false);
377 pixelsacquired = true;
378 }
379 } else {
380 pixelsacquired = false;
381 }
382
383 // Decide if ficus should be watered
384 wateringtimedif = now.unixtime() - lastwatering.unixtime();
385
386 if ((now.hour() > WateringStart.substring(0, 2).toInt() || (now.hour() == WateringStart.substring(0, 2).toInt() && now.minute() > WateringStart.substring(WateringStart.length() - 2, WateringStart.length()).toInt())) && (now.hour() < WateringEnd.substring(0, 2).toInt() || (now.hour() == WateringEnd.substring(0, 2).toInt() && now.minute() < WateringEnd.substring(WateringEnd.length() - 2, WateringEnd.length()).toInt()))) {
387 if (wateringtimedif > MinimumWateringInterval && GetValuesAvailable(StoredMoistureValues, MoistureAveragingWindow) == MoistureAveragingWindow && AverageMoistureLevel >= MoistureThreshold && MoistureSD <= MoistureSDThreshold) {
388 //if (false) {
389
390 // Turn on Titrator
391 TitratorOn();
392
393 // Decide whether water should be supplemented with fertilizer
394 if (GreenPixelsVpercentage < 10) {
395 AddFertilizer = true;
396 } else {
397 AddFertilizer = false;
398 }
399
400 // Water ficus
401 WaterFicus(WateringVolume, AddFertilizer);
402
403 lastwatering = now;
404 wateredonce = true;
405
406 // Turn off Titrator
407 TitratorOff();
408
409 } else {
410 if (wateringtimedif < MinimumWateringInterval) {
411 elapseddays = (wateringtimedif - (wateringtimedif % (60*60*24))) / (60*60*24);
412 elapsedhours = (wateringtimedif - (wateringtimedif % (60*60)) - elapseddays*60*60*24) / (60*60);
413 elapsedminutes = (wateringtimedif - (wateringtimedif % (60)) - elapseddays*60*60*24 - elapsedhours*60*60) / (60);
414 elapsedseconds = (wateringtimedif - elapseddays*60*60*24 - elapsedhours*60*60 - elapsedminutes*60);
415 if (wateredonce == false) {
416 Serial.print(F("Initialization has been "));
417 Serial.print(elapseddays);
418 Serial.print(F(" day(s), "));
419 Serial.print(elapsedhours);
420 Serial.print(F(" hour(s), "));
421 Serial.print(elapsedminutes);
422 Serial.print(F(" minute(s) and "));
423 Serial.print(elapsedseconds);
424 Serial.println(F(" second(s) ago!"));
425 } else {
426 Serial.print(F("Last watering has only been "));
427 Serial.print(elapseddays);
428 Serial.print(F(" day(s), "));
429 Serial.print(elapsedhours);
430 Serial.print(F(" hour(s), "));
431 Serial.print(elapsedminutes);
432 Serial.print(F(" minute(s) and "));
433 Serial.print(elapsedseconds);
434 Serial.println(F(" second(s) ago!"));
435 }
436 }
437 if (AverageMoistureLevel <= MoistureThreshold) {
438 // Serial.println("Moisture level (" + String(AverageMoistureLevel) + "%) is still above threshold (" + String(int(MoistureThreshold)) + "%)!"); // Use if working with percent.
439 Serial.print(F("Moisture level ("));
440 Serial.print(AverageMoistureLevel);
441 Serial.println(F(") is still above threshold!"));
442 }
443 }
444 }
445
446 // Set ThingSpeak fields with the values
447 if (VerboseOut == true) Serial.println(F("Setting ThingSpeak fields..."));
448 ThingSpeak.setField(1, (int) round(AverageLightLevel));
449 ThingSpeak.setField(2, (int) round(AverageMoistureLevel));
450 ThingSpeak.setField(3, WaterLevel);
451 if (cameraupdate == true) {
452 ThingSpeak.setField(4, GreenPixelsVpercentage);
453 ThingSpeak.setField(6, GreenPixelsLpercentage);
454 ThingSpeak.setField(7, GreenPixelsIpercentage);
455 statusadd = "\nGreen pixels acquired.";
456 cameraupdate = false;
457 } else {
458 statusadd = "";
459 }
460 ThingSpeak.setField(5, MoistureSD);
461 ThingSpeak.setField(8, CurrentLightLevel);
462
463 // Set ThingSpeak channel status
464 if (VerboseOut == true) Serial.println(F("Setting ThingSpeak channel status..."));
465 if (wateredonce == false) {
466 ThingSpeak.setStatus(datetimestring(now) + "\nOperational. Initialized: " + datetimestring(lastwatering) + statusadd);
467 } else {
468 ThingSpeak.setStatus(datetimestring(now) + "\nOperational. Last watering: " + datetimestring(lastwatering) + statusadd) + ", Seconds since last watering: " + wateringtimedif;
469 }
470
471 // Write to the ThingSpeak channel
472 if (VerboseOut == true) Serial.println(F("Updating ThingSpeak channel..."));
473 /*
474 ThingSpeakResponse = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
475 if(ThingSpeakResponse == 200){
476 Serial.println(F("Channel update successful."));
477 }
478 else{
479 Serial.print(F("Problem updating channel. HTTP error code "));
480 Serial.println(ThingSpeakResponse);
481 }
482 */
483 oldminutes = now.minute();
484 }
485 else {
486 waiting = true;
487 delay(1000);
488 Serial.print(".");
489 }
490
491}
492
493// FUNCTIONS
494
495// DEBUG
496void timestampDebug() {
497 time_t test;
498 String timestamp;
499 while (true) {
500 //if (rtc.now().unixtime() != nowUTC) {
501 //nowUTC = rtc.now().unixtime();
502 timestamp = "1585684200";
503 test = (time_t) timestamp.toInt();
504 nowLocal = UK.toLocal(test); // Convert time according to day light saving time rule
505 Serial.print(F("test: "));
506 Serial.print(test);
507 Serial.print(F(", nowLocal: "));
508 Serial.print(nowLocal);
509 Serial.print(F(", nowUTC is DST: "));
510 Serial.println(UK.utcIsDST(test));
511 DateTime timestampDateTime = timestamp.toInt();
512 DateTime nowLocalDateTime = nowLocal;
513 Serial.print(F("timestampDateTime: "));
514 Serial.print(datetimestring(timestampDateTime));
515 Serial.println(F(", nowLocal: "));
516 //Serial.println("timestampDateTime: " + datetimestring(timestampDateTime) + ", nowLocalDateTime: " + datetimestring(nowLocalDateTime));
517 //}
518 }
519}
520
521// Function for watering ficus with Titrator
522void WaterFicus(int TotalVolume, boolean Fertilizer) {
523 int LeftSteps;
524 int RightSteps;
525 int FertilizerVolume;
526 // Initialize Titrator
527 TitratorInit();
528
529 // Set syringe default speed
530 SetSyringeSpeed("Left", 10);
531 SetSyringeSpeed("Right", 10);
532
533 if (Fertilizer == true) {
534 // Add water plus fertilizer
535 if (TotalVolume >= WaterSyringeLimit) {
536 for (int times = 1; times <= TotalVolume / WaterSyringeLimit; times++) {
537 LeftSteps = int(WaterSyringeLimit / float(WaterSyringeVolume) * 1000);
538 FertilizerVolume = WaterSyringeLimit * FertilizerToWaterRatio * 1000;
539 RightSteps = int(FertilizerVolume / float(FertilizerSyringeVolume) * 1000);
540 // Pick up water and fertilizer, 1000 = full stroke
541 Serial.print(F("Picking up "));
542 Serial.print(WaterSyringeLimit);
543 Serial.print(F(" mL water and "));
544 Serial.print(FertilizerVolume);
545 Serial.println(F(" uL fertilizer."));
546 TitratorPickUpBoth(LeftSteps, RightSteps, 10, 10);
547 // Dispense water and fertilizer, 1000 = full stroke
548 Serial.print(F("Dispensing "));
549 Serial.print(WaterSyringeLimit);
550 Serial.print(F(" mL water and "));
551 Serial.print(FertilizerVolume);
552 Serial.println(F(" uL fertilizer."));
553 TitratorDispenseBoth(LeftSteps, RightSteps, 10, 10);
554 }
555 }
556 if (TotalVolume % WaterSyringeLimit != 0) {
557 LeftSteps = int((TotalVolume % WaterSyringeLimit) / float(WaterSyringeVolume) * 1000);
558 FertilizerVolume = (TotalVolume % WaterSyringeLimit) * FertilizerToWaterRatio * 1000;
559 RightSteps = int(FertilizerVolume / float(FertilizerSyringeVolume) * 1000);
560 // Pick up remaining water
561 Serial.print(F("Picking up "));
562 Serial.print(TotalVolume % WaterSyringeLimit);
563 Serial.print(F(" mL water and "));
564 Serial.print(FertilizerVolume);
565 Serial.println(F(" uL fertilizer."));
566 TitratorPickUpBoth(LeftSteps, RightSteps, 10, 10);
567 // Dispense remaining
568 Serial.print(F("Dispensing "));
569 Serial.print(TotalVolume % WaterSyringeLimit);
570 Serial.print(F(" mL water and "));
571 Serial.print(FertilizerVolume);
572 Serial.println(F(" uL fertilizer."));
573 TitratorDispenseBoth(LeftSteps, RightSteps, 10, 10);
574 }
575 } else {
576 // Add water only
577 if (TotalVolume >= WaterSyringeLimit) {
578 for (int times = 1; times <= TotalVolume / WaterSyringeLimit; times++) {
579 LeftSteps = int(WaterSyringeLimit / float(WaterSyringeVolume) * 1000);
580 // Pick up water, 1000 = full stroke
581 Serial.print(F("Picking up "));
582 Serial.print(WaterSyringeLimit);
583 Serial.println(F(" mL water."));
584 TitratorPickUp("Left", LeftSteps, 10);
585 // Dispense water, 1000 = full stroke
586 Serial.println(F("Dispensing "));
587 Serial.println(WaterSyringeLimit);
588 Serial.println(F(" mL water."));
589 TitratorDispense("Left", LeftSteps, 10);
590 }
591 }
592 if (TotalVolume % WaterSyringeVolume != 0) {
593 LeftSteps = int((TotalVolume % WaterSyringeLimit) / float(WaterSyringeVolume) * 1000);
594 // Pick up remaining water
595 Serial.println(F("Picking up "));
596 Serial.println(TotalVolume % WaterSyringeLimit);
597 Serial.println(F(" mL water."));
598 TitratorPickUp("Left", LeftSteps, 10);
599 // Dispense remaining
600 Serial.println(F("Dispensing "));
601 Serial.println(TotalVolume % WaterSyringeLimit);
602 Serial.println(F(" mL water."));
603 TitratorDispense("Left", LeftSteps, 10);
604 }
605 }
606}
607
608// Function to update set of previous values
609void UpdatePreviousValues(int Values[], int ValuesLength, int NewValue) {
610 int PreviousValues = 0;
611 for (int i = 0; i < ValuesLength; i++) {
612 if (Values[i] == 0) {
613 Values[i] = NewValue; // Store current value at first empty array postion
614 break;
615 }
616 PreviousValues = PreviousValues + 1;
617 }
618 if (PreviousValues == ValuesLength) {
619 memmove(Values, &Values[1], sizeof(int) * ValuesLength - sizeof(int)); // Shift array values to the left
620 Values[ValuesLength - 1] = NewValue; // Store current value at end of array
621 }
622}
623
624// Function to get number of previous sensor values available within given window
625// Returns number of values available
626int GetValuesAvailable(int Values[], int ValuesLength) {
627 int ValuesAvailable = 0;
628 for (int i = 0; i < ValuesLength; i++) {
629 if (Values[i] != 0) {
630 ValuesAvailable = ValuesAvailable + 1;
631 } else {
632 break;
633 }
634 }
635 return ValuesAvailable;
636}
637
638// Function to calculate average sensor value based on previous data points
639// Returns average
640float GetValuesAverage(int Values[], int ValuesLength) {
641 float ValuesAverage;
642 float WindowSum = 0;
643 int ValuesAvailable = 0;
644 for (int i = 0; i < ValuesLength; i++) {
645 if (Values[i] != 0) {
646 WindowSum = WindowSum + Values[i];
647 ValuesAvailable = ValuesAvailable + 1;
648 } else {
649 break;
650 }
651 }
652 ValuesAverage = WindowSum / float(ValuesAvailable);
653 return ValuesAverage;
654}
655
656// Function to calculate standard deviation of previous data points
657float GetValuesSD(int Values[], int ValuesLength) {
658 float ValuesSD;
659 float WindowSum = 0;
660 int ValuesAvailable = 0;
661 float ValuesAverage = GetValuesAverage(Values, ValuesLength);
662 for (int i = 0; i < ValuesLength; i++) {
663 if (Values[i] != 0) {
664 WindowSum = WindowSum + sq(Values[i] - ValuesAverage);
665 ValuesAvailable = ValuesAvailable + 1;
666 } else {
667 break;
668 }
669 }
670 ValuesSD = sqrt(WindowSum / (ValuesAvailable - 1));
671 return ValuesSD;
672}
673
674// Function to send an NTP request to the time server at the given address
675void sendNTPpacket(char *ntpSrv)
676{
677 // Set all bytes in the buffer to 0
678 memset(packetBuffer, 0, NTP_PACKET_SIZE);
679 // Initialize values needed to form NTP request
680
681 packetBuffer[0] = 0b11100011; // LI, Version, Mode
682 packetBuffer[1] = 0; // Stratum, or type of clock
683 packetBuffer[2] = 6; // Polling Interval
684 packetBuffer[3] = 0xEC; // Peer Clock Precision
685 // 8 bytes of zero for Root Delay & Root Dispersion
686 packetBuffer[12] = 49;
687 packetBuffer[13] = 0x4E;
688 packetBuffer[14] = 49;
689 packetBuffer[15] = 52;
690
691 // All NTP fields have been given values, now a packet can be sent requesting a timestamp:
692 Udp.beginPacket(ntpSrv, 123); //NTP requests are to port 123
693 Udp.write(packetBuffer, NTP_PACKET_SIZE);
694 Udp.endPacket();
695}
696
697// Function to check if Titrator is busy or ready
698void TitratorCheckReady() {
699 if (VerboseOut == true) Serial.println(F("Checking Titrator status..."));
700 boolean confirmed = false;
701 while (confirmed == false) {
702 Serial2.print("aFR\r");
703 delay(100);
704 getAnswer(answer);
705 if (VerboseOut == true) {
706 Serial.print(F("Echo: "));
707 Serial.print(answer[0]);
708 Serial.print(F(", Response: "));
709 Serial.println(answer[1]);
710 }
711 if(answer[1].indexOf("Y") > 0) {
712 Serial.println(F("Ready."));
713 confirmed = true;
714 }
715 }
716}
717
718// Function to dispense liquid with Titrator using both channels
719void TitratorDispenseBoth(int StepsLeft, int StepsRight, int SecondsLeft, int SecondsRight) {
720 boolean confirmed = false;
721 if (VerboseOut == true) Serial.println(F("Dispensing liquid..."));
722 // Setting both valves to output position
723 String command = "aBOCOR\r";
724 while (confirmed == false) {
725 Serial2.print(command);
726 delay(100);
727 getAnswer(answer);
728 if (VerboseOut == true) {
729 Serial.print(F("Echo: "));
730 Serial.print(answer[0]);
731 Serial.print(F(", Response: "));
732 Serial.println(answer[1]);
733 }
734 if (answer[1] == "<ACK>") {
735 confirmed = true;
736 }
737 }
738 TitratorCheckReady();
739 confirmed = false;
740 command = "a";
741 command.concat("B");
742 command.concat("D");
743 command.concat(StepsLeft);
744 command.concat("S");
745 command.concat(SecondsLeft);
746 command.concat("C");
747 command.concat("D");
748 command.concat(StepsRight);
749 command.concat("S");
750 command.concat(SecondsRight);
751 command.concat("R\r");
752 while (confirmed == false) {
753 Serial2.print(command);
754 delay(100);
755 getAnswer(answer);
756 if (VerboseOut == true) {
757 Serial.print(F("Echo: "));
758 Serial.print(answer[0]);
759 Serial.print(F(", Response: "));
760 Serial.println(answer[1]);
761 }
762 if (answer[1] == "<ACK>") {
763 confirmed = true;
764 }
765 }
766 TitratorCheckReady();
767}
768
769// Function to dispense liquid with Titrator
770void TitratorDispense(String channel, int steps, int seconds) {
771 // Setting opposite valve to input position to prevent reflux
772 if (VerboseOut == true) Serial.println(F("Setting opposite valve to input position..."));
773 boolean confirmed = false;
774 String command = "a";
775 if (channel == "Left") {
776 command.concat("C");
777 }
778 if (channel == "Right") {
779 command.concat("B");
780 }
781 command.concat("IR\r");
782 while (confirmed == false) {
783 Serial2.print(command);
784 delay(100);
785 getAnswer(answer);
786 if (VerboseOut == true) {
787 Serial.print(F("Echo: "));
788 Serial.print(answer[0]);
789 Serial.print(F(", Response: "));
790 Serial.println(answer[1]);
791 }
792 if (answer[1] == "<ACK>") {
793 confirmed = true;
794 }
795 }
796 TitratorCheckReady();
797 confirmed = false;
798 // Dispensing liquid
799 if (VerboseOut == true) Serial.println(F("Dispensing liquid..."));
800 command = "a";
801 if (channel == "Left") {
802 command.concat("B");
803 }
804 if (channel == "Right") {
805 command.concat("C");
806 }
807 command.concat("OD");
808 command.concat(steps);
809 command.concat("S");
810 command.concat(seconds);
811 command.concat("R\r");
812 while (confirmed == false) {
813 Serial2.print(command);
814 delay(100);
815 getAnswer(answer);
816 if (VerboseOut == true) {
817 Serial.print(F("Echo: "));
818 Serial.print(answer[0]);
819 Serial.print(F(", Response: "));
820 Serial.println(answer[1]);
821 }
822 if (answer[1] == "<ACK>") {
823 confirmed = true;
824 }
825 }
826 TitratorCheckReady();
827}
828
829// Function to pick up liquid with Titrator using both channels
830void TitratorPickUpBoth(int StepsLeft, int StepsRight, int SecondsLeft, int SecondsRight) {
831 if (VerboseOut == true) Serial.println(F("Picking up liquid..."));
832 boolean confirmed = false;
833 String command = "a";
834 command.concat("B");
835 command.concat("IP");
836 command.concat(StepsLeft);
837 command.concat("S");
838 command.concat(SecondsLeft);
839 command.concat("C");
840 command.concat("IP");
841 command.concat(StepsRight);
842 command.concat("S");
843 command.concat(SecondsRight);
844 command.concat("R\r");
845 while (confirmed == false) {
846 Serial2.print(command);
847 delay(100);
848 getAnswer(answer);
849 if (VerboseOut == true) {
850 Serial.print(F("Echo: "));
851 Serial.print(answer[0]);
852 Serial.print(F(", Response: "));
853 Serial.println(answer[1]);
854 }
855 if (answer[1] == "<ACK>") {
856 confirmed = true;
857 }
858 }
859 TitratorCheckReady();
860}
861
862// Function to pick up liquid with Titrator
863void TitratorPickUp(String channel, int steps, int seconds) {
864 if (VerboseOut == true) Serial.println(F("Picking up liquid..."));
865 boolean confirmed = false;
866 String command = "a";
867 if (channel == "Left") {
868 command.concat("B");
869 }
870 if (channel == "Right") {
871 command.concat("C");
872 }
873 command.concat("IP");
874 command.concat(steps);
875 command.concat("S");
876 command.concat(seconds);
877 command.concat("R\r");
878 while (confirmed == false) {
879 Serial2.print(command);
880 delay(100);
881 getAnswer(answer);
882 if (VerboseOut == true) {
883 Serial.print(F("Echo: "));
884 Serial.print(answer[0]);
885 Serial.print(F(", Response: "));
886 Serial.println(answer[1]);
887 }
888 if (answer[1] == "<ACK>") {
889 confirmed = true;
890 }
891 }
892 TitratorCheckReady();
893}
894
895// Function to set Titrator default Syringe Speed
896void SetSyringeSpeed(String channel, int seconds) {
897 if (VerboseOut == true) Serial.println(F("Setting syringe default speed..."));
898 boolean confirmed = false;
899 String command = "a";
900 if (channel == "Left") {
901 command.concat("B");
902 }
903 if (channel == "Right") {
904 command.concat("C");
905 }
906 command.concat("BYSS");
907 command.concat(seconds);
908 command.concat("R\r");
909 while (confirmed == false) {
910 Serial2.print(command);
911 delay(100);
912 getAnswer(answer);
913 if (VerboseOut == true) {
914 Serial.print(F("Echo: "));
915 Serial.print(answer[0]);
916 Serial.print(F(", Response: "));
917 Serial.println(answer[1]);
918 }
919 if (answer[1] == "<ACK>") {
920 confirmed = true;
921 }
922 }
923}
924
925// Function to initialize Titrator and wait until Titrator is initialized
926void TitratorInit() {
927 // Initializing Titrator
928 if (VerboseOut == true) Serial.println(F("Initializing titrator..."));
929 boolean confirmed = false;
930 while (confirmed == false) {
931 Serial2.print("aXR\r");
932 delay(100);
933 getAnswer(answer);
934 if (VerboseOut == true) {
935 Serial.print(F("Echo: "));
936 Serial.print(answer[0]);
937 Serial.print(F(", Response: "));
938 Serial.println(answer[1]);
939 }
940 if (answer[1] == "<ACK>") {
941 confirmed = true;
942 }
943 }
944 TitratorCheckReady();
945}
946
947// Function to turn on Titrator and wait until Titrator is turned on
948void TitratorOn() {
949 PowerOn();
950 // Trying to assign Titrator address as a test
951 if (VerboseOut == true) Serial.println(F("Auto-addressing Titrator..."));
952 boolean confirmed = false;
953 while (confirmed == false) {
954 Serial2.print("1a\r");
955 delay(100);
956 getAnswer(answer);
957 if (VerboseOut == true) {
958 Serial.print(F("Echo: "));
959 Serial.print(answer[0]);
960 Serial.print(F(", Response: "));
961 Serial.println(answer[1]);
962 }
963 if (answer[1] == "<ACK>") {
964 if (VerboseOut == true) Serial.println(F("Titrator is on."));
965 confirmed = true;
966 }
967 }
968}
969
970// Function to turn off Titrator and wait until Titrator is turned off
971void TitratorOff() {
972 PowerOff();
973 // Trying to assign Titrator address as a test
974 if (VerboseOut == true) Serial.println(F("Auto-addressing Titrator..."));
975 boolean confirmed = false;
976 while (confirmed == false) {
977 Serial2.print("1a\r");
978 delay(100);
979 getAnswer(answer);
980 if (VerboseOut == true) {
981 Serial.print(F("Echo: "));
982 Serial.print(answer[0]);
983 Serial.print(F(", Response: "));
984 Serial.println(answer[1]);
985 }
986 if (answer[1] != "<ACK>") {
987 if (VerboseOut == true) Serial.println(F("Titrator is off."));
988 confirmed = true;
989 }
990 }
991}
992
993// Function to turn on mains
994void PowerOn() {
995 if (VerboseOut == true) Serial.println(F("\nTurning on power..."));
996 lw_send(on);
997}
998
999// Function to turn off mains
1000void PowerOff() {
1001 if (VerboseOut == true) Serial.println(F("Turning off power..."));
1002 lw_send(off);
1003}
1004
1005// Function to get data received from Titrator
1006// Returns array containing echo [0] and response [1]
1007void getAnswer(String answer[]) {
1008 String echo;
1009 String response;
1010 boolean echoReceived = 0;
1011 while (Serial2.available() > 0) {
1012 if (VerboseOut == true) Serial.println(F("Serial2 available."));
1013 String received = convertOutput(Serial2.read());
1014 if (received == "<CR>" && echoReceived == 0) {
1015 echoReceived = 1;
1016 } else if (received != "<CR>") {
1017 if (echoReceived == 0) {
1018 echo.concat(received);
1019 } else {
1020 response.concat(received);
1021 }
1022 }
1023 }
1024 answer[0] = echo;
1025 answer[1] = response;
1026}
1027
1028// Function to detect and convert control commands in Titrator output
1029// Returns Titrator output converted to String
1030String convertOutput(int input){
1031 String output;
1032 if (input < 32) {
1033 switch (int(input)){
1034 case 6:
1035 output = "<ACK>";
1036 break;
1037 case 13:
1038 output = "<CR>";
1039 break;
1040 case 21:
1041 output = "<NAK>";
1042 break;
1043 }
1044 }
1045 else {
1046 output = String(char(input));
1047 }
1048 return output;
1049}
1050
1051// Function to get water reservoir level based on distance from ultrasonic sensor
1052// Returns water reservoir level in mL
1053int getWaterLevel(){
1054 long duration, watermm, waterml;
1055 // The sensor is triggered by a HIGH pulse of 10 or more microseconds.
1056 // Give a short LOW pulse beforehand to ensure a clean HIGH pulse:
1057 digitalWrite(trigPin, LOW);
1058 delayMicroseconds(5);
1059 digitalWrite(trigPin, HIGH);
1060 delayMicroseconds(10);
1061 digitalWrite(trigPin, LOW);
1062
1063 // Read the signal from the sensor: a HIGH pulse whose
1064 // duration is the time (in microseconds) from the sending
1065 // of the ping to the reception of its echo off of an object.
1066 pinMode(echoPin, INPUT);
1067 duration = pulseIn(echoPin, HIGH);
1068
1069 // Convert the time into a distance
1070 watermm = (duration/2) / 29.1 * 10; // Divide by 29.1 or multiply by 0.0343
1071 waterml = WaterReservoirVolume * ((watermm - (float) MinWaterLevelDistance) / ((float) MaxWaterLevelDistance - (float) MinWaterLevelDistance));
1072 // Round according to defined accuracy limit
1073 waterml = waterml - waterml % WaterLevelOutputAccuracy;
1074 if (waterml < 0) {
1075 waterml = 0;
1076 }
1077 if (VerboseOut == true) {
1078 Serial.print(F("Water level in duration: "));
1079 Serial.println(duration);
1080 Serial.print(F("Water level in mm distance: "));
1081 Serial.println(watermm);
1082 Serial.print(F("Water level in ml: "));
1083 Serial.println(waterml);
1084 }
1085 return waterml;
1086}
1087
1088// Function to get number of green pixels
1089// Updates global variables
1090void getGreenPixels (boolean withLEDs) {
1091 uint8_t r, g, b;
1092
1093 pixy.getResolution();
1094 int frameWidth = pixy.frameWidth; // Should be 316
1095 int frameHeight = pixy.frameHeight; // Should be 208
1096
1097 //Get percentage of mainly green pixels in image
1098 GreenPixelsL = 0;
1099 YellowPixelsL = 0;
1100 GreenPixelsV = 0;
1101 YellowPixelsV = 0;
1102 GreenPixelsI = 0;
1103 YellowPixelsI = 0;
1104 progress = progressstep;
1105 if (withLEDs) {
1106 pixy.setLamp(1, 0); //Switch on lamp
1107 }
1108 pixy.setCameraBrightness(127);
1109 Serial.println(F("\nCamera acquiring... 0%"));
1110 for (int y = 0; y <= frameHeight - 1; y++) {
1111 //Serial.print("Line: ");
1112 //Serial.println(y);
1113 for (int x = 0; x <= frameWidth - 1; x++) {
1114 currentprogress = y * float(frameWidth - 1) + x;
1115 if (currentprogress / (float(frameHeight) * float(frameWidth)) * 100.0 >= progress) {
1116 Serial.print(F("Camera acquiring... "));
1117 Serial.print(progress);
1118 Serial.println("%");
1119 progress = progress + progressstep;
1120 }
1121 pixy.video.getRGB(x, y, &r, &g, &b, false);
1122
1123 getHue(r, g, b, 'L');
1124 // Uncomment this print command to obtain full picture data via Serial Monitor
1125 //Serial.print(String(r) + " " + String(g) + " " + String(b) + " " + String(round(H)) + " " + String(round(S)) + " " + String(round(V)) + ";");
1126 if (H >= 60.0 && H <= 157.5 && S >= 33.0 && V >= 33.0 && V <= 100.0) {
1127 GreenPixelsL++;
1128 }
1129 else if (H >= 35.0 && H < 60.0 && S >= 33.0 && V >= 33.0 && V <= 100.0) {
1130 YellowPixelsL++;
1131 }
1132
1133 getHue(r, g, b, 'V');
1134 // Uncomment this print command to obtain full picture data via Serial Monitor
1135 //Serial.print(String(r) + " " + String(g) + " " + String(b) + " " + String(round(H)) + " " + String(round(S)) + " " + String(round(V)) + ";");
1136 if (H >= 60.0 && H <= 157.5 && S >= 33.0 && V >= 33.0 && V <= 100.0) {
1137 GreenPixelsV++;
1138 }
1139 else if (H >= 35.0 && H < 60.0 && S >= 33.0 && V >= 33.0 && V <= 100.0) {
1140 YellowPixelsV++;
1141 }
1142
1143 getHue(r, g, b, 'I');
1144 // Uncomment this print command to obtain full picture data via Serial Monitor
1145 //Serial.print(String(r) + " " + String(g) + " " + String(b) + " " + String(round(H)) + " " + String(round(S)) + " " + String(round(V)) + ";");
1146 if (H >= 60.0 && H <= 157.5 && S >= 33.0 && V >= 33.0 && V <= 100.0) {
1147 GreenPixelsI++;
1148 }
1149 else if (H >= 35.0 && H < 60.0 && S >= 33.0 && V >= 33.0 && V <= 100.0) {
1150 YellowPixelsI++;
1151 }
1152
1153 }
1154 }
1155 if (withLEDs) {
1156 pixy.setLamp(0, 0); //Switch off lamp
1157 }
1158 GreenPixelsLpercentage = GreenPixelsL / (float(frameWidth) * float(frameHeight)) * 100.0;
1159 YellowPixelsLpercentage = YellowPixelsL / (float(frameWidth) * float(frameHeight)) * 100.0;
1160 GreenPixelsVpercentage = GreenPixelsV / (float(frameWidth) * float(frameHeight)) * 100.0;
1161 YellowPixelsVpercentage = YellowPixelsV / (float(frameWidth) * float(frameHeight)) * 100.0;
1162 GreenPixelsIpercentage = GreenPixelsI / (float(frameWidth) * float(frameHeight)) * 100.0;
1163 YellowPixelsIpercentage = YellowPixelsI / (float(frameWidth) * float(frameHeight)) * 100.0;
1164 Serial.print(F("Percentage of green pixels: "));
1165 Serial.print(GreenPixelsLpercentage);
1166 Serial.print(F("% (HSL), "));
1167 Serial.print(GreenPixelsVpercentage);
1168 Serial.print(F("% (HSV), "));
1169 Serial.print(GreenPixelsIpercentage);
1170 Serial.println(F("% (HSI)"));
1171 Serial.print(F("Percentage of yellow pixels: "));
1172 Serial.print(YellowPixelsLpercentage);
1173 Serial.print(F("% (HSL), "));
1174 Serial.print(YellowPixelsVpercentage);
1175 Serial.print(F("% (HSV), "));
1176 Serial.print(YellowPixelsIpercentage);
1177 Serial.println(F("% (HSI)"));
1178 cameraupdate = true;
1179}
1180
1181// Function to concatenate date and time information
1182// Returns String with date and time
1183String datetimestring(DateTime datetime) {
1184 String fulldatetime;
1185 fulldatetime = String(datetime.year(), DEC) + "/";
1186 if (datetime.month() < 10) {
1187 fulldatetime += "0";
1188 }
1189 fulldatetime += String(datetime.month(), DEC) + "/";
1190 if (datetime.day() < 10) {
1191 fulldatetime += "0";
1192 }
1193 fulldatetime += String(datetime.day(), DEC) + " ";
1194 if (datetime.hour() < 10) {
1195 fulldatetime += "0";
1196 }
1197 fulldatetime += String(datetime.hour(), DEC) + ":";
1198 if (datetime.minute() < 10) {
1199 fulldatetime += "0";
1200 }
1201 fulldatetime += String(datetime.minute(), DEC) + ":";
1202 if (datetime.second() < 10) {
1203 fulldatetime += "0";
1204 }
1205 fulldatetime += String(datetime.second(), DEC);
1206 return fulldatetime;
1207}
1208
1209// Function to get HSB from RGB
1210// Updates global variables
1211int getHue(int Rin, int Gin, int Bin, char Mode) {
1212 float hue;
1213 float saturation;
1214 float luminance;
1215 float value;
1216 float intensity;
1217 float R = Rin / 255.0;
1218 float G = Gin / 255.0;
1219 float B = Bin / 255.0;
1220 float Max = max(R, max(G, B));
1221 float Min = min(R, min(G, B));
1222 float C = Max - Min;
1223 if (C == 0) {
1224 hue = 0.0;
1225 } else {
1226 if (Max == R) {
1227 hue = (G - B) / C;
1228 // Ideally should be ((G - B) / C) % 6, but mod operator can only be used for integers, so use the following instead:
1229 if (hue < 0) {
1230 hue = hue + 6;
1231 }
1232 } else if (Max == G) {
1233 hue = (B - R) / C + 2;
1234 } else {
1235 hue = (R - G) / C + 4;
1236 }
1237 // Calculate H, S, B in range from 0 to 1
1238 hue = hue * 60.0 / 360.0;
1239 // Calculate brightness as either luminance, value or intensity
1240 switch (Mode) {
1241 case 'L':
1242 luminance = (Max + Min) / 2.0; // Bi-hexcone model
1243 break;
1244 case 'V':
1245 value = max(R, max(G, B)); // Value, used by ImageJ
1246 break;
1247 case 'I':
1248 intensity = (R + G + B) / 3.0; // Arithmetic mean
1249 break;
1250 }
1251 // Calculate saturation based on brightness as either luminance, value or intensity
1252 switch (Mode) {
1253 case 'L':
1254 if (luminance == 1 || luminance == 0) {
1255 saturation = 0;
1256 } else {
1257 saturation = C / (1.0 - abs(2.0 * luminance - 1.0));
1258 }
1259 break;
1260 case 'V':
1261 // Saturation calculated based on Value (as in ImageJ)
1262 if (value == 0) {
1263 saturation = 0;
1264 } else {
1265 saturation = C / value;
1266 }
1267 break;
1268 case 'I':
1269 // Saturation calculated based on Intensity
1270 if (intensity == 0) {
1271 saturation = 0;
1272 } else {
1273 saturation = 1.0 - Min / intensity;
1274 }
1275 break;
1276 }
1277 }
1278
1279 // Calcualte H, S, B in range from 0 to 255
1280 H = round(hue * 255);
1281 S = round(saturation * 255);
1282 switch (Mode) {
1283 case 'L':
1284 V = round(luminance * 255);
1285 break;
1286 case 'V':
1287 V = round(value * 255);
1288 break;
1289 case 'I':
1290 V = round(intensity * 255);
1291 break;
1292 }
1293 // Set range max for H to 360 and for S and B to 100
1294 H = H / 255.0 * 360.0;
1295 S = S / 255.0 * 100.0;
1296 V = V / 255.0 * 100.0;
1297 /*
1298 if (VerboseOut == true) {
1299 switch (Mode) {
1300 case 'L':
1301 Serial.println("HSL: " + String(H) + ", " + String(S) + ", " + String(V));
1302 break;
1303 case 'V':
1304 Serial.println("HSV: " + String(H) + ", " + String(S) + ", " + String(V));
1305 break;
1306 case 'I':
1307 Serial.println("HSI: " + String(H) + ", " + String(S) + ", " + String(V));
1308 break;
1309 }
1310 }
1311 */
1312}