Ok, so I've written more code than I hoped to, as the problem is a litle trickier that anticipated. I've tried to comment as best a possible, but I'll surmise here in bullet points:
- Take a reading every 5 seconds
- Keep a running average of 10 readings
- Keep a trend on whether it is getting dryer or wetter
- If either trend is strong, change state if needed
- Only change, though if there has been a reasonable shift in the trend (+/- 10)
- If there is a large downwards shift (indicating a heavy shower), close asap
- If the average is high, keep open, or low, keep closed. This is to avoid a scenario where, for example is it still raining, but not as bad as before. I want to keep the door closed even if the sensor is getting a little dryer.
To be frank, I'm not too happy with it, but my limited testing has worked well enough, but not perfectly. Sometimes the door opens or closes when it probably shouldn't. I'm not sure of a good way to get a lot of good test data so I could simulate how it is working.
Any feedback appreciated, thanks,
Stephen
/*
Take 10 readings and use the average to work out a trend of whether it is getting wetter or dryer.
Open (if the trend is dryer and door is closed) or close (if the trend is wetter and the door is open) the door.
If there is a big drop in the reading, close the door immediately
Only actually change state if there is a decent movement on the trend from the time period after the last state
change (up or down 10 points)
TODO - There could be a dryer trend, but it could still be raining... maybe just set a threshold to just keep the
door closed, but that threshold will need to be worked out with some real world testing.
Replace LEDs with relays to control the DC motor.
*/
#define sensorPower 7
#define sensorAnalogPin A0
int greenLED = 3;
int redLED = 4;
int buzzer = 9;
const String OPEN = "OPEN";
const String CLOSED = "CLOSED";
int analogAverage[10]; // Store the most recent 10 readings
int analogTrend[10]; // is it getting wetter (+1) or dryer (-1)
int arrayCount = 0; // use this to loop over the two arrays above
int lastTrendValue = 0;
int previousValue;
int lastStateChangeValue;
String state = OPEN;
void setup() {
pinMode(sensorPower, OUTPUT);
pinMode(greenLED, OUTPUT);
pinMode(redLED, OUTPUT);
pinMode(buzzer, OUTPUT);
// Initially keep the sensor OFF
digitalWrite(sensorPower, LOW);
Serial.begin(9600);
resetArrays();
}
void loop() {
analogAverage[arrayCount] = readSensor(); // read sensor value into array
int valAverage = arrayAverage(analogAverage, 10);
boolean shouldOpen = shouldDoorOpenOrClose(valAverage);
// increment, and if needed reset, the array counter
incrementAndResetArrayCount();
controlShutterDoor(shouldOpen, valAverage);
delay(5000); // Take a reading every 5 seconds
}
void resetArrays() {
// fill average array with current reading over 5 seconds
for (int i = 0; i < 10; i++) {
analogAverage[i] = readSensor();
analogTrend[i] = 0; // reset trend to 0
delay(500);
}
previousValue = arrayAverage(analogAverage, 10);
lastStateChangeValue = previousValue;
}
/**
return false to close the door, true to open.
*/
bool shouldDoorOpenOrClose(int valAverage) {
// is the value on an upwards or downwards trend?
Serial.print("valAverage = ");
Serial.print(valAverage);
Serial.print(", previousValue = ");
Serial.println(previousValue);
if (valAverage > previousValue) { // getting dryer
analogTrend[arrayCount] = -1;
lastTrendValue = -1;
} else if (valAverage < previousValue) { // getting wetter
analogTrend[arrayCount] = 1;
lastTrendValue = 1;
} else {
analogTrend[arrayCount] = lastTrendValue; // set this so that we don't hold on to previous trend value
}
arrayAverage(analogTrend, 10); // just calling this for now to print out for debugging info
// short cut out on very wet or very dry
if (valAverage > 850) {
// almost certainly quite dry even if the sensor is getting a bit moister
shouldOpen = true;
} else (valAverage < 450) {
// almost certainly quite wet even if the sensor is getting a bit dryer
shouldOpen = false;
}
// count the trend array to see if it is mostly getting wetter or dryer
int dryerCount = 0;
int wetterCount = 0;
for (int i = 0; i < 10; i++) {
if (analogTrend[i] == -1) {
dryerCount++;
} else if (analogTrend[i] == 1) {
wetterCount++;
}
}
int delta = previousValue - valAverage;
previousValue = valAverage;
if (delta > 20) { // significant drop, close door asap
return false;
}
if (dryerCount > 6) {
return true;
} else if (wetterCount > 6) {
return false;
}
}
void controlShutterDoor(bool shouldOpen, int valAverage) {
int delta = abs(valAverage - lastStateChangeValue); // only if long term trend moves the average up or down by 10 should we open/close the door
Serial.println();
Serial.print("Last state change value = ");
Serial.println(lastStateChangeValue);
if (shouldOpen) {
Serial.println("Status: Clear");
if (CLOSED == state) {
if (delta > 10) {
// open the door, takes about 20 seconds, lots of extra space to make this easier to see in serial monitor
Serial.println(" Opening the door, green light");
flashAndSoundBuzzer(greenLED, 100);
state = OPEN;
onStateChange(5000); // change to 5 minutes IRL on opening
}
}
} else { // should close
Serial.println("Status: It's raining");
if (OPEN == state) {
if (delta > 10) {
// close the door, takes about 20 seconds
Serial.println(" Closing the door, red light");
flashAndSoundBuzzer(redLED, 100);
state = CLOSED;
onStateChange(5000); // change to 30 minutes IRL on closing
}
}
}
}
void onStateChange(int delayMS) {
delay(delayMS);
resetArrays();
}
int readSensor() {
//get the reading from the sensor and print it
digitalWrite(sensorPower, HIGH); // Turn the sensor ON
delay(20); // Allow power to settle
int valAnalog = analogRead(sensorAnalogPin);
digitalWrite(sensorPower, LOW); // Turn the sensor OFF
Serial.print("Analog Output: ");
Serial.println(valAnalog);
return valAnalog;
}
int arrayAverage(int values[], int size) {
int total = 0;
for ( int k = 0 ; k < size ; ++k ) {
total += values[k];
Serial.print(values[k]);
Serial.print(", ");
}
return total / size;
}
void incrementAndResetArrayCount() {
arrayCount++;
if (arrayCount > 9) {
arrayCount = 0;
}
}
void flashAndSoundBuzzer(int ledPin, int buzzFrequency) {
for (int i = 0; i < 10; i++) {
digitalWrite(ledPin, HIGH);
tone(buzzer, buzzFrequency, 500);
delay(500);
digitalWrite(ledPin, LOW);
delay(500);
}
}