de | en
C++ etc.

ARDUINO

That's still a work in progress...

Follow the Sun

Idea for pointing a solar panel precisely at the sun with three sensors.
Parts designed in Tinkercad printed on Prusa MK4.

Stacks Image 4898787

#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);

UV Dosimeter

Determine the exact exposure time in the sun (UV) of cyanotypes.
Based on an idea and some code by Wagner Lungov.

Stacks Image 4898732

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

Cold Frame Project

Greenhouse control system for succulents with automated heating, irrigation, and nighttime CO₂ release.

Stacks Image 4898880
Stacks Image 4898883
Stacks Image 4898886
Stacks Image 4898887

// 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()  {}
Arduino | Hardy Hemmi