Idea for pointing a solar panel precisely at the sun with three sensors.
Parts designed in Tinkercad printed on Prusa MK4.
#include "thingProperties.h"
#define SensorA A3
#define SensorB A4
#define SensorC A5
#define numAverageA 180
#define numAverageB 180
#define numAverageC 180
int valuesA[numAverageA], countAverageA = 0;
int valuesB[numAverageB], countAverageB = 0;
int valuesC[numAverageC], countAverageC = 0;
const int stepPinX = 2; //2
const int dirPinX = 5; //5
const int stepPinY = 3; //3
const int dirPinY = 6; //6
int valHysteresis = 5;
int speed = 200;
//panMotor = 0;
//tiltMotor = 0;
int actMotor = 100;
bool executed;
unsigned long startzeit = 0;
void setup() {
// Initialize serial and wait for port to open:
Serial.begin(9600);
// This delay gives the chance to wait for a Serial Monitor without blocking if none is found
delay(1500);
// Defined in thingProperties.h
initProperties();
// Connect to Arduino IoT Cloud
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
setDebugMessageLevel(2);
ArduinoCloud.printDebugInfo();
pinMode(stepPinX, OUTPUT);
pinMode(dirPinX, OUTPUT);
pinMode(stepPinY, OUTPUT);
pinMode(dirPinY, OUTPUT);
pinMode(8, OUTPUT); // Setze Pin 8 als Ausgang
//pinMode(SensorA, INPUT);
// pinMode(SensorB, INPUT);
// pinMode(SensorC, INPUT);
}
void loop() {
ArduinoCloud.update();
valueA = analogRead(A3);
valueB = analogRead(A4);
valueC = analogRead(A5);
Serial.println(valueA);
fvalueA = averageA(valueA);
fvalueB = averageB(valueB);
fvalueC = averageC(valueC);
status = digitalRead(8);
//Serial.print("panMotor:");
Serial.print(panMotor);
Serial.print(",");
//Serial.print("tiltMotor:");
Serial.print(tiltMotor);
Serial.print(",");
// Serial.print("Max:");
Serial.print(1023);
Serial.print(",");
//Serial.print("Min:");
Serial.print(0);
//Serial.print(",");
//Serial.print("Sensor-A:");
Serial.print(fvalueA);
Serial.print(",");
//Serial.print("Sensor-B:");
Serial.print(fvalueB);
Serial.print(",");
//Serial.print("Sensor-C:");
Serial.print(fvalueC);
Serial.print(",");
//Serial.print("Hysteresis:");
Serial.println(valHysteresis);
Serial.println(",");
// –––––––––– PAN ––––––––––
if (abs(fvalueA - fvalueB) > valHysteresis) {
if ((digitalRead(8) == HIGH) && (executed == false)){
digitalWrite (8, LOW);
startzeit = millis();
executed = true;
}
if (fvalueA > fvalueB) {
digitalWrite(dirPinX, HIGH); // was HIGH
panMotor = actMotor;
} else {
digitalWrite(dirPinX, LOW); // was LOW
panMotor = -actMotor;
}
digitalWrite(stepPinX, HIGH); // was HIGH
delayMicroseconds(speed);
digitalWrite(stepPinX, LOW); // was LOW
delayMicroseconds(speed);
}
if (abs(fvalueA - fvalueB) < valHysteresis){
panMotor = 0;
if (millis() > startzeit+2000){
digitalWrite(8, HIGH);
executed = false;
}
}
// –––––––––– TILT ––––––––––
if (abs((fvalueA / 2 + fvalueB / 2) - abs(fvalueC)) > valHysteresis) {
if ((digitalRead(8) == HIGH) && (executed == false)){
digitalWrite (8, LOW);
startzeit = millis();
executed = true;
}
if ((fvalueA / 2 + fvalueB / 2) > fvalueC) {
digitalWrite(dirPinY, HIGH); // was LOW
tiltMotor = -actMotor;
} else {
digitalWrite(dirPinY, LOW); // was HIGH
tiltMotor = actMotor;
}
digitalWrite(stepPinY, LOW); // was HIGH
delayMicroseconds(speed);
digitalWrite(stepPinY, HIGH); // was LOW
delayMicroseconds(speed);
}
if (abs((fvalueA / 2 + fvalueB / 2) - abs(fvalueC)) < valHysteresis) {
tiltMotor = 5;
if (millis() > startzeit+2000){
digitalWrite(8, HIGH);
Serial.println("Jetzt ist HIGH");
executed = false;
}
}
}
// ––––––––––––––––––––––––––––––––––––––––––––––––––––––
float averageA(int newValueA) {
float averA, sumA = 0;
valuesA[countAverageA] = newValueA;
for (int k = 0; k < numAverageA; k++) sumA += valuesA[k];
averA = (float)sumA / numAverageA;
countAverageA++;
if (countAverageA >= numAverageA) countAverageA = 0;
return averA;
}
float averageB(int newValueB) {
float averB, sumB = 0;
valuesB[countAverageB] = newValueB;
for (int k = 0; k < numAverageB; k++) sumB += valuesB[k];
averB = (float)sumB / numAverageB;
countAverageB++;
if (countAverageB >= numAverageB) countAverageB = 0;
return averB;
}
float averageC(int newValueC) {
float averC, sumC = 0;
valuesC[countAverageC] = newValueC;
for (int k = 0; k < numAverageC; k++) sumC += valuesC[k];
averC = (float)sumC / numAverageC;
countAverageC++;
if (countAverageC >= numAverageC) countAverageC = 0;
return averC;
}
// ––––––––––––––––––––––––––––––––––––––––––––––––––––––
// ––––––––––––––––––––––––––––––––––––––––––––––––––––––
// ––––––––––––––––––––––––––––––––––––––––––––––––––––––
// below the things-stuff
// Code generated by Arduino IoT Cloud, DO NOT EDIT.
#include
#include
const char SSID[] = SECRET_SSID; // Network SSID (name)
const char PASS[] = SECRET_OPTIONAL_PASS; // Network password (use for WPA, or use as key for WEP)
int fvalueA;
int fvalueB;
int fvalueC;
int panMotor;
int tiltMotor;
int valueA;
int valueB;
int valueC;
bool status;
void initProperties(){
ArduinoCloud.addProperty(fvalueA, READ, 2 * SECONDS, NULL);
ArduinoCloud.addProperty(fvalueB, READ, 2 * SECONDS, NULL);
ArduinoCloud.addProperty(fvalueC, READ, 2 * SECONDS, NULL);
ArduinoCloud.addProperty(panMotor, READ, ON_CHANGE, NULL);
ArduinoCloud.addProperty(tiltMotor, READ, ON_CHANGE, NULL);
ArduinoCloud.addProperty(valueA, READ, 2 * SECONDS, NULL);
ArduinoCloud.addProperty(valueB, READ, 2 * SECONDS, NULL);
ArduinoCloud.addProperty(valueC, READ, 2 * SECONDS, NULL);
ArduinoCloud.addProperty(status, READ, ON_CHANGE, NULL);
}
WiFiConnectionHandler ArduinoIoTPreferredConnection(SSID, PASS);
Determine the exact exposure time in the sun (UV) of cyanotypes.
Based on an idea and some code by Wagner Lungov.
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// –––––––––––––––––––––––––––––––––––––––– UV Dosimeter based on Wagner Lungov ––––––––––––––––––––––––––––––––––––––––
// –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// –––––––––––––––––––––––––––––––––––––––– Arduino Mega Board 2560 R3 ––––––––––––––––––––––––––––––––––––––––
// rotary encoder ––– A = PWM6 | B = PWM7 | C = PWM5 | VCC = 3.3 | GND = GND
// uv sensor ML 8511 ––– EN = A0 | OUT = A1 | GND = GND | 3V3 = 3.3V | VIN = empty | EN connected to 3V3
// 1.5inch OLED Module ––– VCC = 5V | GND = GND | DIN = 51 | CLK = 52| CS = PWM13 | DC = PWM12 | RST = PWM11
// passive buzzer ––– SIGNAL(+) = PWM2 | GND to GND
#include
U8G2_SSD1327_MIDAS_128X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/13, /* dc=*/12, /* reset=*/11);
#define outputA 6
#define outputB 7
int counter = 0;
int aState;
int aLastState;
int buttonPin = 5;
boolean isButtonPushDown(void) {
if (!digitalRead(buttonPin)) {
delay(30);
if (!digitalRead(buttonPin))
return true;
}
return false;
}
// –––––––––––––––––––––––––––––––––––––––– variables ––––––––––––––––––––––––––––––––––––––––
int r_sun = 4;
int pgr_bar = 0;
int tm[4];
bool stt = 0;
int uvLevel;
int uvLevel_avg[11];
float uvLevel_cur_avg;
int uvLevel_shift = 200;
float expo;
float expo_so_far;
float expo_left;
long int t_remaining;
float const conv = .003787878;
int pos = 0;
char ckn;
int const k_avg = 3;
long int t_next;
bool pause = 0;
int const buzz = 2;
int const uv_out = A1;
int const uv_ena = A0;
int uv_out_mW = A1;
int uv_ena_mW = A0; // was A0
// –––––––––––––––––––– my variables ––––––––––––––––––––
int steps = 50;
int uvLevel_alt;
int minuten;
bool flag = false;
long int t_start;
long int t_estimated;
long int t_passed;
int ts[4];
int tc[4];
int te[4];
int maxsun;
int minsun;
int unic;
int dezc;
int cenc;
// –––––––––––––––––––– variables graph ––––––––––––––––––––
const int analogInPin = A1;
int analogInValue = A0;
int x;
int y[128];
int uvLevel_mW;
int refLevel_mW;
int uvIntensity_mW;
int mWcm;
int vltOut;
float outputVoltage_mW;
unsigned char anz = 0;
bool buzzflag = true;
unsigned char i, j;
// –––––––––––––––––––––––––––––––––––––––– setup ––––––––––––––––––––––––––––––––––––––––
void (*resetFunc)(void) = 0; // reset with rotary
void setup() {
Serial.begin(38400);
pinMode(outputA, INPUT);
pinMode(outputB, INPUT);
pinMode(buttonPin, INPUT);
aLastState = digitalRead(outputA);
// –––––––––––––––––––– screen ––––––––––––––––––––
u8g2.begin();
u8g2.setFontMode(1);
u8g2.setDrawColor(1);
u8g2.setContrast(254);
expo_so_far = 0;
pinMode(uv_out, INPUT);
pinMode(buzz, OUTPUT);
pinMode(uv_ena, INPUT);
digitalWrite(uv_ena, HIGH);
x = 0; // setup graph
clearY(); // setup graph
for (int i = 0; i < k_avg; i++) {
uvLevel = averageAnalogRead(uv_out) - uvLevel_shift;
if (uvLevel < 0) uvLevel = 0;
uvLevel_avg[i] = uvLevel;
u8g2.firstPage();
}
delay(5);
t_next = millis() + 1000;
faz_tela();
}
// –––––––––––––––––––––––––––––––––––––––– end setup start loop ––––––––––––––––––––––––––––––––––––––––
void loop() {
int uvLevel_mW = averageAnalogRead(uv_out_mW);
int refLevel_mW = averageAnalogRead(uv_ena_mW);
float outputVoltage_mW = 3.3 / refLevel_mW * uvLevel_mW;
float uvIntensity_mW = mapfloat(outputVoltage_mW, 0.99, 2.8, 0.0, 15.0);
mWcm = (uvIntensity_mW * 1000);
vltOut = (outputVoltage_mW * 1000);
//------------rotary encoder
aState = digitalRead(outputA);
if (aState != aLastState) {
if (digitalRead(outputB) != aState) {
counter++;
} else {
counter--;
}
}
aLastState = aState;
if (counter < 0) (counter = 0);
unic = (counter % 10);
dezc = ((counter / 10) % 10);
cenc = ((counter / 100) % 10);
if (isButtonPushDown()) {
expo = counter;
t_start = millis(); // remember starttime (millis) on # key pressed
if (expo > 0) {
stt = 1;
expo_so_far = 0;
} else {
delay(0);
}
}
// –––––––––––––––––––– starting timer ––––––––––––––––––––
if (isButtonPushDown()) {
expo = counter;
if (expo > 0) {
expo_left = expo;
uvLevel_cur_avg = 0;
for (int i = 0; i < k_avg; i++) { uvLevel_cur_avg = uvLevel_cur_avg + uvLevel_avg[i]; }
uvLevel_cur_avg = uvLevel_cur_avg / k_avg;
t_remaining = round(expo_left / (conv * uvLevel_cur_avg));
cd_mmss(t_remaining);
faz_tela();
} else {
delay(0);
}
}
if (millis() > t_next) {
t_next = t_next + 1000;
uvLevel = averageAnalogRead(uv_out) - uvLevel_shift;
for (int i = 0; i < k_avg - 1; i++) { uvLevel_avg[i] = uvLevel_avg[i + 1]; } // shift the last readings
uvLevel_avg[k_avg - 1] = uvLevel; // add the last
uvLevel_cur_avg = 0;
for (int i = 0; i < k_avg; i++) { uvLevel_cur_avg = uvLevel_cur_avg + uvLevel_avg[i]; }
uvLevel_cur_avg = uvLevel_cur_avg / k_avg;
if (pause == 0) r_sun = map(uvLevel_cur_avg, 0, 220, 1, 16);
else r_sun = 1;
if (stt == 1) {
if (pause == 0) { expo_so_far = expo_so_far + conv * uvLevel_cur_avg; }
expo_left = expo - expo_so_far;
t_remaining = round(expo_left / (conv * uvLevel_cur_avg));
if (t_remaining < 0) t_remaining = 0;
cd_mmss(t_remaining);
t_passed = millis() - t_start;
cc_mmss((t_estimated - (t_passed / 1000)) - t_remaining); // info if faster or slower than expected
// –––––––––––––––––––– t_estimated, min / max sun remeber ––––––––––––––––––––
if (flag == false) {
u8g2.setFont(u8g2_font_freedoomr10_mu);
t_estimated = t_remaining;
(maxsun = uvLevel_cur_avg);
(minsun = maxsun);
flag = true;
}
// Sonne Minimum Maximum set
u8g2.setFont(u8g2_font_freedoomr10_mu);
u8g2.setCursor(96, 52);
u8g2.print(0);
u8g2.setCursor(96, 66);
u8g2.print(0);
// progress bar update
pgr_bar = map(expo_so_far, 0, expo, 0, 126);
if (expo_so_far > expo) {
ce_mmss(t_passed / 1000);
stt = 0;
expo_so_far = 0;
pgr_bar = 0;
done();
}
}
faz_tela();
}
}
// –––––––––––––––––––––––––––––––––––––––– end loop start functions ––––––––––––––––––––––––––––––––––––––––
int averageAnalogRead(int pinToRead) {
byte numberOfReadings = 8;
unsigned int runningValue = 0;
for (int x = 0; x < numberOfReadings; x++) runningValue += analogRead(pinToRead);
runningValue = runningValue / numberOfReadings;
return (runningValue);
}
void faz_tela() {
u8g2.firstPage();
if (mWcm < 0) (mWcm = 0);
u8g2.setFont(u8g2_font_baby_tf);
u8g2.setCursor(78, 125);
u8g2.print(float(mWcm) / 1000);
u8g2.setCursor(95, 125);
u8g2.print("mW/cm2");
//Serial.println(float(vltOut)/1000);
do {
u8g2.drawLine(0, 68, 128, 68);
u8g2.drawLine(0, 127, 128, 127);
u8g2.drawLine(0, 68, 0, 127);
u8g2.drawLine(127, 68, 127, 127);
for (int i = 1; i < 64; 2 * i++) {
// u8g2.drawPixel(i * 2, 82);
u8g2.drawPixel(i * 2, 96);
// u8g2.drawPixel(i * 2, 110);
u8g2.drawPixel(32, 68 + i * 2);
u8g2.drawPixel(64, 68 + i * 2);
u8g2.drawPixel(96, 68 + i * 2);
u8g2.setDrawColor(0);
u8g2.drawLine(96, 120, 96, 126);
u8g2.setDrawColor(1);
}
// the line
analogInValue = analogRead(analogInPin);
y[x] = map(analogInValue * 2, 0, 1023, 64 - 1, 0);
x++;
if (x >= 128) {
x = 0;
clearY();
}
// end of the line
drawY();
//–––––––––––––––––––– arrows ––––––––––––––––––––
if (t_estimated > t_remaining) u8g2.drawTriangle(73, 60, 79, 53, 67, 53);
if (t_estimated < t_remaining) u8g2.drawTriangle(73, 42, 80, 50, 66, 50);
//–––––––––––––––––––– progress bar ––––––––––––––––––––
if (t_remaining < t_estimated) {
u8g2.drawLine(99, 64, 99, (t_remaining * 64 / t_estimated));
u8g2.drawLine(100, 64, 100, (t_remaining * 64 / t_estimated));
u8g2.drawLine(101, 64, 101, (t_remaining * 64 / t_estimated));
}
// –––––––––––––––––– bar graph + uv level––––––––––––––––––––––
if (uvLevel_cur_avg > 4 * steps) {
u8g2.drawBox(107, 11, 20, 9);
}
if (uvLevel_cur_avg > 3 * steps) {
u8g2.drawBox(107, 22, 20, 9);
}
if (uvLevel_cur_avg > 2 * steps) {
u8g2.drawBox(107, 33, 20, 9);
}
if (uvLevel_cur_avg > steps) {
u8g2.drawBox(107, 44, 20, 9);
}
if (uvLevel_cur_avg > 1) {
u8g2.drawBox(107, 55, 20, 9);
}
if (uvLevel_cur_avg < 10) u8g2.setCursor(120, 12);
if (uvLevel_cur_avg > 9) u8g2.setCursor(112, 12);
if (uvLevel_cur_avg > 99) u8g2.setCursor(104, 12);
u8g2.setFont(u8g2_font_freedoomr10_mu);
u8g2.print(round(uvLevel_cur_avg));
uvLevel_alt = (round(uvLevel_cur_avg));
//
minuten = tm[0];
u8g2.setFont(u8g2_font_freedoomr25_mn);
if (minuten < 9) {
u8g2.setCursor(0, 26);
u8g2.print(tm[0]);
u8g2.setCursor(23, 26);
u8g2.print(tm[1]);
//u8g2.setCursor(41, 22); u8g2.print(':');
u8g2.drawBox(47, 7, 3, 5);
u8g2.drawBox(47, 17, 3, 5);
u8g2.setCursor(54, 26);
u8g2.print(tm[2]);
u8g2.setCursor(77, 26);
u8g2.print(tm[3]);
}
if (minuten > 9) {
u8g2.setCursor(0, 26);
u8g2.print('-');
u8g2.setCursor(23, 26);
u8g2.print('-');
//u8g2.setCursor(41, 26); u8g2.print(':');
u8g2.drawBox(47, 7, 3, 5);
u8g2.drawBox(47, 17, 3, 5);
u8g2.setCursor(54, 26);
u8g2.print('-');
u8g2.setCursor(77, 26);
u8g2.print('-');
}
u8g2.setFont(u8g2_font_freedoomr25_mn);
u8g2.setCursor(0, 66);
u8g2.print(cenc);
u8g2.setCursor(22, 66);
u8g2.print(dezc);
u8g2.setCursor(44, 66);
u8g2.print(unic);
// u8g2.drawLine(pos * 22, 65, pos * 22 + 18, 65);
// u8g2.drawLine(pos * 22, 64, pos * 22 + 18, 64);
u8g2.setFont(u8g2_font_freedoomr10_mu);
if (t_estimated == t_remaining) {
u8g2.setCursor(2, 39);
u8g2.print(tm[0]);
u8g2.setCursor(10, 39);
u8g2.print(tm[1]);
u8g2.setCursor(18, 39);
u8g2.print(':');
u8g2.setCursor(26, 39);
u8g2.print(tm[2]);
u8g2.setCursor(34, 39);
u8g2.print(tm[3]);
ts[0] = tm[0];
ts[1] = tm[1];
ts[2] = tm[2];
ts[3] = tm[3];
} else {
u8g2.setCursor(2, 39);
u8g2.print(ts[0]);
u8g2.setCursor(10, 39);
u8g2.print(ts[1]);
u8g2.setCursor(18, 39);
u8g2.print(':');
u8g2.setCursor(26, 39);
u8g2.print(ts[2]);
u8g2.setCursor(34, 39);
u8g2.print(ts[3]);
}
// show ecent correctio + sun min an max
if (flag == true) {
if (t_estimated > t_remaining) {
u8g2.setCursor(46, 39);
u8g2.print('-');
}
if (t_estimated < t_remaining) {
u8g2.setCursor(46, 39);
u8g2.print('+');
}
u8g2.setCursor(56, 39);
u8g2.print(abs(tc[0]));
u8g2.setCursor(64, 39);
u8g2.print(abs(tc[1]));
u8g2.setCursor(72, 39);
u8g2.print(':');
u8g2.setCursor(80, 39);
u8g2.print(abs(tc[2]));
u8g2.setCursor(88, 39);
u8g2.print(abs(tc[3]));
if (uvLevel_cur_avg > maxsun) (maxsun = uvLevel_cur_avg);
if (maxsun < 10) u8g2.setCursor(88, 52);
if (maxsun > 9) u8g2.setCursor(80, 52);
if (maxsun > 99) u8g2.setCursor(72, 52);
u8g2.print(round(maxsun));
if (uvLevel_cur_avg < minsun) (minsun = uvLevel_cur_avg);
if (minsun < 10) u8g2.setCursor(88, 66);
if (minsun > 9) u8g2.setCursor(80, 66);
if (minsun > 99) u8g2.setCursor(72, 66);
u8g2.print(round(minsun));
}
if (flag == false) {
u8g2.setCursor(56, 39);
u8g2.print(abs(te[0]));
u8g2.setCursor(64, 39);
u8g2.print(abs(te[1]));
u8g2.setCursor(72, 39);
u8g2.print(':');
u8g2.setCursor(80, 39);
u8g2.print(abs(te[2]));
u8g2.setCursor(88, 39);
u8g2.print(abs(te[3]));
if (flag == false) {
if (maxsun < 10) u8g2.setCursor(88, 52);
if (maxsun > 9) u8g2.setCursor(80, 52);
if (maxsun > 99) u8g2.setCursor(72, 52);
u8g2.print(round(maxsun));
if (minsun < 10) u8g2.setCursor(88, 66);
if (minsun > 9) u8g2.setCursor(80, 66);
if (minsun > 99) u8g2.setCursor(72, 66);
u8g2.print(round(minsun));
}
}
} while (u8g2.nextPage());
if (isButtonPushDown()) {
if (counter == 0) { // reset procedure
resetFunc();
}
}
}
// –––––––––––––––––––––––––––––––––––––––– functions ––––––––––––––––––––––––––––––––––––––––
void cd_mmss(float t) { // time remaining
tm[0] = t / 600;
tm[1] = (t - tm[0] * 600) / 60;
tm[2] = (t - tm[0] * 600 - tm[1] * 60) / 10;
tm[3] = (t - tm[0] * 600 - tm[1] * 60 - tm[2] * 10);
}
void cc_mmss(float t) // time deviation
{
tc[0] = t / 600;
tc[1] = (t - tc[0] * 600) / 60;
tc[2] = (t - tc[0] * 600 - tc[1] * 60) / 10;
tc[3] = (t - tc[0] * 600 - tc[1] * 60 - tc[2] * 10);
}
void ce_mmss(float t) // time used
{
te[0] = t / 600;
te[1] = (t - te[0] * 600) / 60;
te[2] = (t - te[0] * 600 - te[1] * 60) / 10;
te[3] = (t - te[0] * 600 - te[1] * 60 - te[2] * 10);
}
void done(void) {
// digitalWrite(buzz, HIGH);
// delay(100);
// digitalWrite(buzz, LOW);
// delay(100);
// digitalWrite(buzz, HIGH);
// delay(300);
// digitalWrite(buzz, LOW);
// delay(100);
// digitalWrite(buzz, HIGH);
// delay(100);
// digitalWrite(buzz, LOW);
// delay(600);
// –––––––––– passive buzzer ––––––––––
if (buzzflag == true) {
for (anz = 1; anz < 4; anz++) {
for (i = 0; i < 120; i++) {
digitalWrite(buzz, HIGH);
delay(1);
digitalWrite(buzz, LOW);
delay(2);
}
for (i = 0; i < 160; i++) {
digitalWrite(buzz, HIGH);
delay(1);
digitalWrite(buzz, LOW);
delay(1);
}
buzzflag = false;
}
}
flag = false;
}
void clearY() {
for (int i = 0; i < 128; i++) {
y[i] = -1;
}
}
void drawY() {
for (int i = 1; i < 128; i++) {
if (y[i] != -1) {
//u8g2.drawPixel(i, y[i]);
u8g2.drawLine(i - 1, y[i - 1] * 2 + 44, i, y[i] * 2 + 44);
} else {
break;
}
}
}
float mapfloat(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
Greenhouse control system for succulents with automated heating, irrigation, and nighttime CO₂ release.
// GIGA R1 minor and major bugfixes (2-23-25) V2 (without graph)
#include "thingProperties.h"
#include
#include
// Relay pins
int RELAY_CARB = 4;
int RELAY_HEAT = 10;
int RELAY_VENT = 11;
// Filter constant
float alpha = 0.1;
float moist;
// Control variables
unsigned long lastMillis = 0;
bool ignoreMeasurements = false;
// UV sensor constant
const float Vref = 4980.0; // Reference voltage in mV (4.98V)
const int adcMax = 1023; // 10-Bit ADC (0–1023)
const float uvOffset = 1548.04; // Adjust offset voltage for darkness
const float conversionFactor = 1000.0 / 200.0; // Conversion factor for mW/cm² (e.g. 1000/200)
// Define time constants
unsigned long ventStartTime = 0; // Time when ventilation started
unsigned long ventStopTime = 0; // Time when ventilation stopped
bool ventRunning = false; // Indicates if ventilation is currently running
bool ventWaiting = false; // Indicates if we are waiting to restart ventilation
// Sensor module
Adafruit_SCD30 scd1;
Adafruit_SCD30 scd2;
#define moistSensor A0
#define lightSensor A7
// ------------------------------------------------------------------------
void setup() {
// Initialize Serial Communication
Serial.begin(115200);
unsigned long serialTimeout = millis();
while (!Serial) {
if (millis() - serialTimeout > 3000) break; // Prevent infinite waiting
delay(10);
}
// Initialize IoT properties and connect to the Arduino Cloud
initProperties();
ArduinoCloud.begin(ArduinoIoTPreferredConnection);
// setDebugMessageLevel(2);
// ArduinoCloud.printDebugInfo();
// Initialize I2C busses
Wire.begin(); // Initialize I2C bus for the first sensor
Wire1.begin(); // Initialize I2C bus for the second sensor
// Initialization for SCD1
int sensorTries1 = 0;
while (!scd1.begin(0x61, &Wire) && sensorTries1 < 5) { // Try initializing up to 5 times
Serial.println("SCD1 not found, retrying...");
delay(100); // Wait 1 second before retrying
sensorTries1++;
}
if (sensorTries1 >= 5) {
Serial.println("SCD1 not detected, continuing without it.");
} else {
Serial.println("SCD1 successfully detected!");
}
// Initialization for SCD2
int sensorTries2 = 0;
while (!scd2.begin(0x61, &Wire1) && sensorTries2 < 5) { // Try initializing up to 5 times
Serial.println("SCD2 not found, retrying...");
delay(100); // Wait 1 second before retrying
sensorTries2++;
}
if (sensorTries2 >= 5) {
Serial.println("SCD2 not detected, continuing without it.");
} else {
Serial.println("SCD2 successfully detected!");
}
scd1.setMeasurementInterval(5);
scd2.setMeasurementInterval(5);
pinMode(lightSensor, INPUT);
// Set relay pins as outputs
pinMode(RELAY_CARB, OUTPUT);
pinMode(RELAY_HEAT, OUTPUT);
pinMode(RELAY_VENT, OUTPUT);
digitalWrite(RELAY_VENT, HIGH); // turn off heat
digitalWrite(RELAY_HEAT, HIGH); // turn off venti
counter = 0;
}
// ------------------------------------------------------------------------
void loop() {
ArduinoCloud.update();
if (scd1.dataReady()) {
//Serial.println("Data available on Sensor 1!");
//Serial.println(scd1.CO2);
if (!scd1.read()) {
Serial.println("Error reading sensor data");
return;
}
}
rawTemperature = ((scd1.temperature));
rawHumidity = ((scd1.relative_humidity));
rawCarbon = (int(scd1.CO2));
if (scd2.dataReady()) {
//Serial.println("Data available on Sensor 2!");
//Serial.println(scd2.CO2);
if (!scd2.read()) {
Serial.println("Error reading sensor data");
return;
}
}
rawtemperatureOut = ((scd2.temperature));
rawhumidityOut = ((scd2.relative_humidity));
rawcarbonOut = (int(scd2.CO2));
mois = analogRead(moistSensor);
moist = (map(analogRead(moistSensor), 704, 285, 0, 1000)); //704,285
moisture = float(moist / 10); // to get one decimal place
// ------------------ smooth not from the beginning --------------------
alpha = 0.1;
if (counter < 25) { // ignore curve smoothing at the beginning
alpha = 1;
carbon = rawCarbon;
carbonOut = rawcarbonOut;
counter++;
}
temperatureOut = exponentialFilter(rawtemperatureOut, temperatureOut, alpha);
humidityOut = exponentialFilter(rawhumidityOut, humidityOut, alpha);
temperature = exponentialFilter(rawTemperature, temperature, alpha);
humidity = exponentialFilter(rawHumidity, humidity, alpha);
if (counter > 24) {
carbon = exponentialFilter(rawCarbon, carbon, alpha);
carbonOut = exponentialFilter(rawcarbonOut, carbonOut, alpha);
}
// -------------------- Calculate dew point --------------------
float rawt = rawTemperature;
float rawh = max(rawHumidity, 0.1); // prevents log(0)
float a = 17.271;
float b = 237.7;
float rawdewPointTemp = (a * rawt) / (b + rawt) + log(rawh / 100);
float denominator = max(a - rawdewPointTemp, 0.0001); // prevents division by 0
float rawp = (b * rawdewPointTemp) / denominator;
rawdewPoint = round(rawp * 10) / 10;
dewPoint = exponentialFilter(rawdewPoint, dewPoint, alpha);
// ------------------- UV index -------------------
// Reduce the number of measurements for faster response time
const int numSamples = 256; // Instead of 1024, reduces wait time to ~0.5 seconds
long sum = 0;
for (int i = 0; i < numSamples; i++) {
sum += analogRead(lightSensor);
delay(2); // Optional: Can be removed if a faster response is needed
}
sum = sum >> 8; // Division by 256 (2^8), replaces slow division with bit-shifting
// Calculate voltage
float voltage = sum * (Vref / adcMax);
float uvVoltage = max(voltage - uvOffset, 0.0); // Prevents negative values
// Convert to UV intensity
rawLight = (uvVoltage / 1000.0) * conversionFactor;
light = exponentialFilter(rawLight, light, alpha);
// -------------------- Disply issue fix --------------------
deltaTemperature = (((temperature - temperatureOut) * 10) / 10);
deltaHumidity = (((humidity - humidityOut) * 10) / 10);
if (abs(temperature) < 0.1) // avoid strange display behaviour on iOS devices
temperature = 0;
if (abs(temperatureOut) < 0.1)
temperatureOut = 0;
if (abs(deltaTemperature) < 0.1)
deltaTemperature = 0;
if (abs(humidity) < 0.1)
humidity = 0;
if (abs(humidityOut) < 0.1)
humidityOut = 0;
if (abs(deltaHumidity) < 0.1)
deltaHumidity = 0;
if (abs(light) < 0.01)
light = 0;
// -------------------- Heat section below 20 °C --------------------
if (heatOn == 0) heatDelta = 0;
if ((heatOn != 0) && (heatDelta == 0)) heatDelta = 175;
if (temperature < 20) {
ventSwitch = false;
ventGraph = 0; // display status
if ((temperature * 100) < (heatOn)) { // kleiner als Einschalttemperatur
digitalWrite(RELAY_HEAT, LOW); // turn on heat
digitalWrite(RELAY_VENT, LOW); // turn on venti
heatSwitch = true; // set heat switch on
heatGraph = 1000;
}
if ((temperature * 100) > (heatOn + heatDelta)) { // grösser als Abschalttemperatur
digitalWrite(RELAY_HEAT, HIGH); // turn off heat
digitalWrite(RELAY_VENT, HIGH); // turn off venti
heatSwitch = false; // set heat switch off
heatGraph = 0; // display status
}
}
// -------------------- Venti section above 20 °C --------------------
if (ventiOn == 100) (ventDuration = 0); // turn off the whole stuff
if (ventiOn == 100) (ventCooldown = 0); // turn off the whole stuff
if ((ventiOn != 100) && (ventDuration == 0)) ventDuration = 20000; // fill ins
if ((ventiOn != 100) && (ventCooldown == 0)) ventCooldown = 300000; // fill ins
if (temperature > 20) {
heatSwitch = false; // Set heat switch off
heatGraph = 0; // Display status
if ((temperature) > ventiOn) { // Greater than switching temperature
if (!ventRunning && !ventWaiting) { // Check if ventilation is not running and not in cooldown
digitalWrite(RELAY_HEAT, HIGH); // Turn off heat
digitalWrite(RELAY_VENT, LOW); // Turn on ventilation
ventSwitch = true; // Set heat switch on
ventGraph = 1000; // Display status
ventRunning = true; // Mark ventilation as running
ventStartTime = millis(); // Record the start time
}
}
// Check if we should stop the ventilation based on the timer
if (ventRunning && (millis() - ventStartTime >= ventDuration)) {
digitalWrite(RELAY_HEAT, HIGH); // Turn off heat
digitalWrite(RELAY_VENT, HIGH); // Turn off ventilation
ventSwitch = false; // Set venti switch off
ventGraph = 0; // Display status
ventRunning = false; // Mark ventilation as stopped
ventStopTime = millis(); // Record the stop time
ventWaiting = true; // Start cooldown
}
// Check if we should restart ventilation after cooldown
if (ventWaiting && (millis() - ventStopTime >= ventCooldown)) {
ventWaiting = false; // Reset waiting state
}
}
// ------------------ CO2-Stuff start ------------------
if (carbonOn == 0) carbonOff = 0;
if ((carbonOn != 0) && (carbonOff == 0)) carbonOff = 900;
if (startTime == 0) endTime = 0; // if "Begin Evening" is set to "all day" or "off"
if (startTime == 2000) endTime = 0; // the "End Morning" display changes to "-----"
unsigned long localTime = ArduinoCloud.getLocalTime();
int totalMinutes = (((localTime / 3600) % 24) * 60) + ((localTime / 60) % 60);
if (totalMinutes > startTime || totalMinutes < endTime) {
unsigned long presentMillis = millis();
if (!ignoreMeasurements && carbon < carbonTarget && !carbonSwitch && carbonOff != 0) {
lastMillis = presentMillis;
digitalWrite(RELAY_CARB, HIGH); // Open relay
carbonSwitch = true;
carbonGraph = 690; // New state
}
if (carbonSwitch && presentMillis - lastMillis >= carbonOn * 1000) {
lastMillis = presentMillis;
digitalWrite(RELAY_CARB, LOW); // Close relay
carbonSwitch = false;
carbonGraph = 300; // New state
ignoreMeasurements = true;
}
if (ignoreMeasurements && presentMillis - lastMillis >= carbonOff * 1000) {
ignoreMeasurements = false; // Messungen wieder aktivieren
}
}
if (totalMinutes > endTime && totalMinutes < startTime) { // if open when reaching endTime
if (carbonSwitch == true){
digitalWrite(RELAY_CARB, LOW); // Close relay
carbonSwitch = false;
carbonGraph = 300; // New state
}
}
// -------------------------- CO2-Stuff end ----------------------------
}
// ---------------------------- Loop end ----------------------------
// ----------------------- Function curve smoothing --------------------
float exponentialFilter(float newValue, float currentValue, float alpha) {
return alpha * newValue + (1 - alpha) * currentValue;
}
// ------------------------- Useless functions ----------------------------
void onTemperatureChange() {}
void onHumidityChange() {}
void onCalibrateChange() {}
void onCalibrationvalueChange() {}
void onCalTempChange() {}
void onCalTempOutChange() {}
void onCalHumidChange() {}
void onCalHumidOutChange() {}
void onHeatOnChange() {}
void onHeatDeltaChange() {}
void onVentiOnChange() {}
void onHeatIsonChange() {}
void onVentiIsonChange() {}
void onCalibrateOutChange() {}
void onCalibrationvalueOutChange() {}
void oncarbonOnChange() {}
void oncarbonOffChange() {}
void onRelayDisplayChange() {}
void onCarbonSwitchChange() {}
void onCarbonTargetChange() {}
void onVentiSwitchChange() {}
void onSwitchChange() {}
void onHeatSwitchChange() {}
void onVentSwitchChange() {}
void onHeatGraphChange() {}
void onVentGraphChange() {}
void onVentDeltaChange() {}
void onLightChange() {}
void onMoistureChange() {}
void onRawlightChange() {}
void onRawLightChange() {}
void onUnindexChange() {}
void onUvindexChange() {}
void onStartTimeChange() {}
void onEndTimeChange() {}
void onCarbonGraphChange() {}
void onDewPointChange() {}
void onVentDurationChange() {}
void onVentCooldownChange() {}
void onVentWaitingChange() {}
void onCarbonOffChange() {}
void onCarbonOnChange() {}
void onCounterChange() {}