Skip to main content

Beweging, Temperatuur Clock en Buttons

Same setup but improved with three buttons to make it completely independed from PC.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>
#include <DS3231.h>

// --- Definitions ---
#define DHTPIN 4       // Pin connected to the DHT11 data pin
#define DHTTYPE DHT11  // Specify DHT11 sensor
#define BUTTON1PIN 8
#define BUTTON2PIN 9
#define BUTTON3PIN 10
#define TREND_DISPLAY_DURATION_MS 600000  // 10 minutes, time to keep the sinal (arrow up/down) in display
#define HISTORICAL_DATA_ENTRIES_COUNT 5   // Define the length of the history (min-max) buffers: so we keep the last N hours of min max data.
#define MAX_HISTORY_STRING_LENGTH 100     // Maximum characters for the history string
#define DEBUG true                        // Set to true to enable debug printing, false to disable

DHT dht(DHTPIN, DHTTYPE);

DS3231 myRTC;
bool century = false;
bool h12Flag;
bool pmFlag;

int count = 0;
int pirPin = 12;  // Pin for the HC-S501 sensor
int pirValue;

LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 16, 2);
unsigned long previousMillis = 0;
const long dhtInterval = 2000;  // Interval for DHT readings

// Custom characters for the LCD arrows
byte downChar[] = {
  B00000,
  B00000,
  B00100,
  B00100,
  B00100,
  B10101,
  B01110,
  B00100
};

byte upChar[] = {
  B00100,
  B01110,
  B10101,
  B00100,
  B00100,
  B00100,
  B00000,
  B00000
};

byte degreeChar[] = {
  B00110,
  B01001,
  B01001,
  B00110,
  B00000,
  B00000,
  B00000,
  B00000
};

byte maxChar[] = {
  B01110,
  B00000,
  B00100,
  B01110,
  B10101,
  B00100,
  B00100,
  B00100
};

byte minChar[] = {
  B00100,
  B00100,
  B00100,
  B10101,
  B01110,
  B00100,
  B00000,
  B01110
};

//------------------------------------------------------------------------------
// Generic MeasurementSensor Class Template
//------------------------------------------------------------------------------
template<typename T>
class MeasurementSensor {
public:
  // Constructor: initializes with an initial measurement value and history capacity N.
  MeasurementSensor(long sigInterval)
    : measurement(0),
      arrow(0),
      arrowMillis(0),
      lowestMeasurement(0),
      highestMeasurement(0),
      signalInterval(sigInterval),
      historyCount(0),
      historyIndex(0) {}

  // Destructor (no longer needed due to no dynamic memory)
  //~MeasurementSensor() {}

  // Update the measurement reading and determine the arrow indicator.
  void update(T newMeasurement, unsigned long currentMillis) {
    // For the first valid measurement, initialize min/max if needed.
    if (lowestMeasurement == static_cast<T>(0) && highestMeasurement == static_cast<T>(0)) {
      lowestMeasurement = newMeasurement;
      highestMeasurement = newMeasurement;
    }

    // Update the current interval's min and max.
    if (newMeasurement < lowestMeasurement) {
      lowestMeasurement = newMeasurement;
    }
    if (newMeasurement > highestMeasurement) {
      highestMeasurement = newMeasurement;
    }

    // Determine arrow indicator logic.
    if (newMeasurement == measurement && currentMillis - arrowMillis >= signalInterval) {
      arrow = 0;
    } else {
      if (newMeasurement < measurement) {
        arrow = 1;  // Down arrow
        arrowMillis = currentMillis;
      }
      if (newMeasurement > measurement) {
        arrow = 2;  // Up arrow
        arrowMillis = currentMillis;
      }
      if (lowestMeasurement != highestMeasurement) {
        if (newMeasurement == this->getLowest()) {
          arrow = 4;  // Indicator for lowest measurement
          arrowMillis = currentMillis;
        }
        if (newMeasurement == this->getHighest()) {
          arrow = 5;  // Indicator for highest measurement
          arrowMillis = currentMillis;
        }
      }
    }

    // Reset arrow if previous value was an invalid initial value.
    if (measurement == static_cast<T>(0)) {
      arrow = 0;
    }

    measurement = newMeasurement;
  }

  // Getters for measurement and arrow.
  T getMeasurement() const {
    return measurement;
  }

  // Returns the lowest measurement seen over the history combined with the current interval.
  T getLowest() const {
    T minVal = lowestMeasurement;
    // Loop over the history array.
    for (size_t i = 0; i < historyCount; i++) {
      if (lowestHistory[i] < minVal) {
        minVal = lowestHistory[i];
      }
    }
    return minVal;
  }

  // Returns the highest measurement seen over the history combined with the current interval.
  T getHighest() const {
    T maxVal = highestMeasurement;
    // Loop over the history array.
    for (size_t i = 0; i < historyCount; i++) {
      if (highestHistory[i] > maxVal) {
        maxVal = highestHistory[i];
      }
    }
    return maxVal;
  }

  int getArrow() const {
    return arrow;
  }

  // Reset the min and max values.
  // The current lowest and highest measurements are stored in the history arrays.
  void resetMinMax(T newMeasurement) {
    // Store the current min and max in the arrays at the current history index.
    lowestHistory[historyIndex] = lowestMeasurement;
    highestHistory[historyIndex] = highestMeasurement;

    lowestMeasurement = newMeasurement;
    highestMeasurement = newMeasurement;

    // Increment historyIndex as a circular buffer.
    historyIndex = (historyIndex + 1) % HISTORICAL_DATA_ENTRIES_COUNT;
    if (historyCount < HISTORICAL_DATA_ENTRIES_COUNT) {
      historyCount++;
    }
  }

  void getHistoryString(bool useHighest, char* buffer, size_t bufferSize) const {
    // Initialize the buffer to an empty string.
    buffer[0] = '\0';

    // Determine the index of the oldest element.
    size_t oldestIndex = (historyCount < HISTORICAL_DATA_ENTRIES_COUNT) ? 0 : historyIndex;

    // Loop over the valid history entries in order.
    for (size_t i = 0; i < historyCount; i++) {
      size_t index = (oldestIndex + i) % HISTORICAL_DATA_ENTRIES_COUNT;
      T value = useHighest ? highestHistory[index] : lowestHistory[index];

      // Temporary buffer for the converted float value.
      char tempValue[10];
      // Convert the float value to a string with width 4 and one decimal point.
      dtostrf(static_cast<double>(value), 4, 1, tempValue);

      // Check if adding the next value will overflow the main buffer.
      size_t remainingSpace = bufferSize - strlen(buffer) - 1;
      size_t charsNeeded = strlen(tempValue) + (i < historyCount - 1 ? 2 : 0);  // value + ", "

      if (charsNeeded > remainingSpace) {
        Serial.println("Error: Insufficient buffer space in getHistoryString!");
        return;  // You may choose to handle this differently.
      }

      // Concatenate the value to the buffer.
      strcat(buffer, tempValue);

      // Add the separator if it's not the last element.
      if (i < historyCount - 1) {
        strcat(buffer, ", ");
      }
    }
  }



private:
  T measurement;
  int arrow;
  unsigned long arrowMillis;
  T lowestMeasurement;
  T highestMeasurement;
  long signalInterval;

  // History storage: fixed size arrays
  T lowestHistory[HISTORICAL_DATA_ENTRIES_COUNT];
  T highestHistory[HISTORICAL_DATA_ENTRIES_COUNT];
  size_t historyCount;
  size_t historyIndex;
};


class Button {
private:
  int pin;
  int prevState;
  const char* name;
  unsigned long pressStartTime;
  bool longPressTriggered;
  const unsigned long LONG_PRESS_DURATION = 1000; // 1 second for long press

public:
  // Constructor: initialize the button pin and its name
  Button(int pin, const char* name)
    : pin(pin), prevState(LOW), name(name), pressStartTime(0), longPressTriggered(false) {
    pinMode(pin, INPUT);
  }

  // Update the button state and return true if the button was pressed (transition from LOW to HIGH)
  bool update() {
    int currentState = digitalRead(pin);
    bool pressed = false;
    
    if (currentState != prevState) {
      // Button state changed
      if (currentState == HIGH) {
        // Button was just pressed
        pressStartTime = millis();
        longPressTriggered = false;
      } else {
        // Button was just released
        if ((millis() - pressStartTime < LONG_PRESS_DURATION) && !longPressTriggered) {
          // Short press detected
          pressed = true;
        }
        pressStartTime = 0;
      }
      prevState = currentState;
    }
    
    return pressed;
  }
  
  // Check for long press and return true if long press is detected
  bool checkLongPress() {
    int currentState = digitalRead(pin);
    
    if (currentState == HIGH && prevState == HIGH) {
      // Button is being held down
      if (millis() - pressStartTime > LONG_PRESS_DURATION && !longPressTriggered) {
        longPressTriggered = true;
        return true;
      }
    }
    
    return false;
  }

  // Getter for the button's name
  const char* getName() {
    return name;
  }
};

class DateTimeSettings {
private:
  // RTC reference
  DS3231& rtc;
  
  // LCD reference
  LiquidCrystal_I2C& lcd;
  
  // Setting state variables
  bool isActive;
  int settingIndex; // 0=day, 1=month, 2=hour, 3=minute
  int currentDay, currentMonth, currentHour, currentMinute;
  
  // Blinking effect variables
  unsigned long lastBlinkTime;
  const unsigned long BLINK_INTERVAL = 500; // 500ms blink interval
  bool showField;
  
  // Reference to century flag needed for RTC
  bool& centuryFlag;
  bool h12Flag, pmFlag; // RTC flags
  
public:
  // Constructor
  DateTimeSettings(DS3231& rtcInstance, LiquidCrystal_I2C& lcdInstance, bool& century)
    : rtc(rtcInstance), 
      lcd(lcdInstance),
      isActive(false),
      settingIndex(0),
      lastBlinkTime(0),
      showField(true),
      centuryFlag(century),
      h12Flag(false),
      pmFlag(false) {
  }
  
  // Start the settings mode
  void start() {
    isActive = true;
    settingIndex = 0; // Start with day
    
    // Get current values from RTC
    currentDay = rtc.getDate();
    currentMonth = rtc.getMonth(centuryFlag);
    currentHour = rtc.getHour(h12Flag, pmFlag);
    currentMinute = rtc.getMinute();
    
    showField = true;
    lastBlinkTime = millis();
    
    displaySettings();
  }
  
  // Check if settings mode is active
  bool isSettingActive() const {
    return isActive;
  }
  
  // Exit settings mode
  void exit() {
    isActive = false;
    lcd.clear();
  }
  
  // Move to the next field
  void nextField() {
    settingIndex++;
    showField = true; // Reset blink state
    
    if (settingIndex > 3) {
      // All fields set, save the time
      rtc.setDate(currentDay);
      rtc.setMonth(currentMonth);
      rtc.setHour(currentHour);
      rtc.setMinute(currentMinute);
      rtc.setSecond(0); // Reset seconds to 00
      
      // Exit setting mode
      exit();
      return;
    }
    
    displaySettings();
  }
  
  // Increase the current field value
  void increaseValue() {
    switch (settingIndex) {
      case 0: // Day
        currentDay++;
        if (currentDay > 31) currentDay = 1;
        break;
      case 1: // Month
        currentMonth++;
        if (currentMonth > 12) currentMonth = 1;
        break;
      case 2: // Hour
        currentHour++;
        if (currentHour > 23) currentHour = 0;
        break;
      case 3: // Minute
        currentMinute++;
        if (currentMinute > 59) currentMinute = 0;
        break;
    }
    showField = true; // Reset blink state
    displaySettings();
  }
  
  // Decrease the current field value
  void decreaseValue() {
    switch (settingIndex) {
      case 0: // Day
        currentDay--;
        if (currentDay < 1) currentDay = 31;
        break;
      case 1: // Month
        currentMonth--;
        if (currentMonth < 1) currentMonth = 12;
        break;
      case 2: // Hour
        currentHour--;
        if (currentHour < 0) currentHour = 23;
        break;
      case 3: // Minute
        currentMinute--;
        if (currentMinute < 0) currentMinute = 59;
        break;
    }
    showField = true; // Reset blink state
    displaySettings();
  }
  
  // Update method to be called in the main loop
  void update() {
    // Return if not in setting mode
    if (!isActive) return;
    
    // Handle blinking effect
    if (millis() - lastBlinkTime >= BLINK_INTERVAL) {
      lastBlinkTime = millis();
      showField = !showField;
      displaySettings();
    }
  }
  
private:
  // Display the settings screen
  void displaySettings() {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Set Date & Time");
    
    lcd.setCursor(1, 1);
    
    // Display day - blinking when being set
    if (settingIndex != 0 || showField) {
      print2digits(currentDay);
    } else {
      lcd.print("  ");
    }
    
    lcd.print("-");
    
    // Display month - blinking when being set
    if (settingIndex != 1 || showField) {
      print2digits(currentMonth);
    } else {
      lcd.print("  ");
    }
    
    lcd.setCursor(8, 1);
    
    // Display hour - blinking when being set
    if (settingIndex != 2 || showField) {
      print2digits(currentHour);
    } else {
      lcd.print("  ");
    }
    
    lcd.print(":");
    
    // Display minute - blinking when being set
    if (settingIndex != 3 || showField) {
      print2digits(currentMinute);
    } else {
      lcd.print("  ");
    }
  }
  
  // Helper function to print 2 digits with leading zero
  void print2digits(int number) {
    if (number < 10) {
      lcd.print("0");
    }
    lcd.print(number, DEC);
  }
};


//------------------------------------------------------------------------------
// Global Instances for Temperature and Humidity
//------------------------------------------------------------------------------
MeasurementSensor<float> tempSensor(TREND_DISPLAY_DURATION_MS);
MeasurementSensor<int> humiditySensor(TREND_DISPLAY_DURATION_MS);

void setup() {
  Serial.begin(9600);
  dht.begin();
  delay(2000);

  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.createChar(1, downChar);
  lcd.createChar(2, upChar);
  lcd.createChar(3, degreeChar);
  lcd.createChar(4, minChar);
  lcd.createChar(5, maxChar);

  pinMode(pirPin, INPUT);

  pinMode(BUTTON1PIN, INPUT);
  pinMode(BUTTON2PIN, INPUT);
  pinMode(BUTTON3PIN, INPUT);

  // myRTC.setYear(2025);
  // myRTC.setMonth(3);
  // myRTC.setDate(20);
  // myRTC.setHour(10);
  // myRTC.setMinute(49);
  // myRTC.setSecond(0);
}

void print2digits(int number) {
  if (number < 10) {
    lcd.print("0");
  }
  lcd.print(number, DEC);
}


int lastResetHour = -1;  // Initialize to an invalid hour

// Create instances for each button
Button button1(BUTTON1PIN, "Button 1");
Button button2(BUTTON2PIN, "Button 2");
Button button3(BUTTON3PIN, "Button 3");

int displayModus = 1;  // we want temp range when 2 we want humidity range

// Add this near the top of your code, with your other global instances
DateTimeSettings dateTimeSettings(myRTC, lcd, century);

void loop() {
  delay(20);

  // Check if we are in setting mode
  if (dateTimeSettings.isSettingActive()) {
    dateTimeSettings.update();
    
    // Check for long press on button 1 to exit setting mode
    if (button1.checkLongPress()) {
      dateTimeSettings.exit();
      return;
    }
    
    // Handle button 1 (short press - move to next field)
    if (button1.update()) {
      dateTimeSettings.nextField();
    }
    
    // Handle button 2 (decrease value)
    if (button2.update()) {
      dateTimeSettings.decreaseValue();
    }
    
    // Handle button 3 (increase value)
    if (button3.update()) {
      dateTimeSettings.increaseValue();
    }
    
    return; // Skip the normal loop when in setting mode
  }

  // Check for long press on button 1 to enter setting mode
  if (button1.checkLongPress()) {
    dateTimeSettings.start();
    return;
  }

  // The rest of your existing loop code stays the same
  // Update each button and print if pressed
  if (button1.update()) {
    Serial.print(button1.getName());
    Serial.println(" pressed");
  }
  if (button2.update()) {
    Serial.print(button2.getName());
    Serial.println(" pressed");
    count = 5;
    previousMillis = 0;
    displayModus = 1;
  }
  if (button3.update()) {
    Serial.print(button3.getName());
    Serial.println(" pressed");
    count = 5;
    previousMillis = 0;
    displayModus = 2;
  }

  // Check for movement
  pirValue = digitalRead(pirPin);
  // Serial.println(pirValue);

  if (pirValue) {
    lcd.backlight();
  }

  // Do we need to update the display?
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= dhtInterval) {
    previousMillis = currentMillis;

    if (!pirValue) {
      lcd.noBacklight();
    }

    count++;

    // Read new values from the DHT sensor
    float newTemperature = dht.readTemperature();
    int newHumidity = dht.readHumidity();

    if (isnan(newTemperature) || isnan(newHumidity)) {
      Serial.println("Failed to read from DHT sensor!");
      lcd.clear();
      lcd.print(" *** DHT Error *** ");
      delay(5000);
      return;  // next itteration in the loop
    }

    // Update our measurement sensors
    tempSensor.update(newTemperature, currentMillis);
    humiditySensor.update(newHumidity, currentMillis);

    // Get current hour from RTC
    int currentHour = myRTC.getHour(h12Flag, pmFlag);
    // Check if hour has changed
    if (currentHour != lastResetHour) {  // || count%10==0

      tempSensor.resetMinMax(newTemperature);
      humiditySensor.resetMinMax(newHumidity);

      lastResetHour = currentHour;
    }

    Serial.println(count);
    char tempHistoryBuffer[MAX_HISTORY_STRING_LENGTH];
    tempSensor.getHistoryString(false, tempHistoryBuffer, sizeof(tempHistoryBuffer));
    Serial.print("min temp history: ");
    Serial.println(tempHistoryBuffer);

    char tempHistoryBuffer2[MAX_HISTORY_STRING_LENGTH];
    tempSensor.getHistoryString(true, tempHistoryBuffer2, sizeof(tempHistoryBuffer2));
    Serial.print("max temp history: ");
    Serial.println(tempHistoryBuffer2);

    Serial.println();


    // Update the LCD display for temperature
    lcd.setCursor(0, 0);
    if (!(count % 6) && displayModus == 1) {
      lcd.print("");
      lcd.print(tempSensor.getLowest(), 1);
      lcd.write(3);
      lcd.print(" ");
      lcd.print(tempSensor.getHighest(), 1);
      lcd.write(3);
      lcd.print(" ");
    } else {
      if (tempSensor.getArrow()) {
        lcd.write(tempSensor.getArrow());
      } else {
        lcd.print(" ");
      }
      lcd.print(tempSensor.getMeasurement(), 1);
      lcd.write(3);  // degree symbol
      lcd.print("C     ");
    }

    // Update the LCD display for humidity
    if (!(count % 6) && displayModus == 2) {
      lcd.setCursor(9, 0);
      lcd.print(humiditySensor.getLowest(), 1);
      lcd.print("% ");
      lcd.print(humiditySensor.getHighest(), 1);
      lcd.print("%");
    } else {
      lcd.setCursor(12, 0);
      if (humiditySensor.getArrow()) {
        lcd.write(humiditySensor.getArrow());
      } else {
        lcd.print(" ");
      }
      lcd.print(humiditySensor.getMeasurement());
      lcd.print("%");
    }

    // Update the LCD display second row, date
    lcd.setCursor(1, 1);
    print2digits(myRTC.getDate());
    lcd.print("-");
    print2digits(myRTC.getMonth(century));

    // Update the LCD display second row, time
    lcd.setCursor(10, 1);
    print2digits(myRTC.getHour(h12Flag, pmFlag));
    lcd.print(":");
    print2digits(myRTC.getMinute());
  }
}

https://www.circuito.io/app?components=97,97,97,514,10167,11022,417986,821989,931983