Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
/* Functionality:
* -- Activate pumps on moisture low
* -- If photoresistor low, and valid hours of day, then turn lights on for 10 minutes
* -- If water level low, then blink lights
*/
#include <Wire.h>
#include <DS3231.h>
// Assumption: Wire assumes that the two communication pins, which are here being used to multiplex
// the two moisture sensors and the real-time clock, are plugged into SDA (A4) and SCL (A5)
/*
* Future wishlist:
* *** Use the moisture sensor's chirp mode as the alarm for low-tank level.
* Make it reset after the water is refilled.
*/
#define NUM_PLANTERS 2
//#define DEBUG // Uncomment this to enable debug printing
//#define SHORTEN_DAY_NIGHT_CYCLE
#ifdef DEBUG
#define DEBUG_PRINT(s) Serial.println(s)
#else
#define DEBUG_PRINT(s)
#endif
/******************** Time ***************************/
#define TICK_TIME 5000 // how often to check moisture level. All times in ms
unsigned int tickCount = 0;
/******************** Clock ***************************/
DS3231 clock;
/************************* Water level **********************/
#define LOW_WATER_THRESHOLD 300
int waterLevelPin = A0;
/*********************** Moisture sensing ************************/
typedef struct {int value;} MoistureLevel;
const int moistResetPin = 2;
const byte moistureSensorAddresses[] = {0x20, 0x21}; // multiplexed; for both pins; AKA 32,33
#define MOISTURE_THRESHOLD 300;
/********************************* Pump control **************************/
int pumpPins[NUM_PLANTERS] = {6, 7};
#define PUMP_TIME 2000 // Time to run each pump for after detecting low moisture. All times in ms
/******************************** Light control ***************************/
#define DARK_THRESHOLD 600
int photoResistorPin = A1;
int growLightPin = 3;
typedef struct {
int value;
} OptionalNonnegInt;
OptionalNonnegInt lastDarkTime;
#ifdef SHORTEN_DAY_NIGHT_CYCLE
#define TICKS_PER_LIGHT_CHECK 1
#else
#define TICKS_PER_LIGHT_CHECK ((1000 * 10 / TICK_TIME) * 60)
#endif
/***************************************************************************************************
*************************************** END HEADER ************************************************
***************************************************************************************************
*/
/************************* Wire library ******************************************/
void writeI2CRegister8bit(int addr, int value) {
// Serial.println("about to use wire");
Wire.beginTransmission(addr);
DEBUG_PRINT("begun transmission"); // for an unknown reason, these print statements seem to help the moisture sensors initialize
Wire.write(value);
DEBUG_PRINT("wrote value to :");
DEBUG_PRINT(addr);
Wire.endTransmission(); //for some reason this needs the reset pin connected?
DEBUG_PRINT("ended transmission, returning to setup");
}
unsigned int readI2CRegister16bit(int addr, int reg) {
Wire.beginTransmission(addr);
Wire.write(reg);
Wire.endTransmission();
delay(1000);
Wire.requestFrom(addr, 2);
unsigned int t = Wire.read() << 8;
t = t | Wire.read();
return t;
}
/***************************** Nonneg-ints ****************************/
OptionalNonnegInt makeEmptyInt() {
OptionalNonnegInt ret;
ret.value = -1;
return ret;
}
OptionalNonnegInt makeNonnegInt(int x) {
OptionalNonnegInt ret;
ret.value = x;
return ret;
}
int isEmpty(OptionalNonnegInt n) {
return n.value == -1;
}
int getValue(OptionalNonnegInt n) {
return n.value;
}
/***************************** Time ****************************/
void setupClock() {
DEBUG_PRINT("setting up clock");
clock.begin();
DEBUG_PRINT("set up clock");
// This line resets the clock to the sketch compiling time
// Uncomment this when configuring a device, then recomment it and upload again
//clock.setDateTime(__DATE__, __TIME__);
}
void printCurTime() {
RTCDateTime dt = clock.getDateTime();
Serial.println("Printing time: Year, month, day, hour, minute");
Serial.println(dt.year);
Serial.println(dt.month);
Serial.println(dt.day);
Serial.println(dt.hour);
Serial.println(dt.minute);
}
int getHourOfDay() {
/*
#ifdef SHORTEN_DAY_NIGHT_CYCLE
RTCDateTime dt = clock.getDateTime();
return ((dt.minute % 2) * 60 + dt.second) / 5;
#else
return clock.getDateTime().hour;
#endif
*/
return 12;
}
OptionalNonnegInt timeSince(OptionalNonnegInt x) {
if (isEmpty(x)) {
return makeEmptyInt();
} else {
int t1 = getValue(x);
int t2 = millis();
return makeNonnegInt(t2 - t1);
}
}
/**************************** Light control ***********************/
void setupLights() {
pinMode(growLightPin, OUTPUT);
}
int isDark() {
int darkLevel = analogRead(photoResistorPin);
DEBUG_PRINT("light level: ");
DEBUG_PRINT(darkLevel);
return darkLevel > DARK_THRESHOLD;
}
int forceDarkenLights() {
int h = getHourOfDay();
DEBUG_PRINT("hour is");
DEBUG_PRINT(h);
return !(h >= 10 && h <= 22);
}
void trySetLights(int status) {
if (forceDarkenLights()) {
DEBUG_PRINT("forcing light to off b/c time of day");
digitalWrite(growLightPin, 0);
} else {
digitalWrite(growLightPin, status);
}
}
void updateDarkTime() {
if ((tickCount % TICKS_PER_LIGHT_CHECK) == 0) {
/* This code solve the problem that it can't test if the room is dark
* when the lights are on.
*
* But....when testing, this flicker is really annoying.
*/
#ifndef SHORTEN_DAY_NIGHT_CYCLE
trySetLights(0);
delay(100);
#endif
if (isDark()) {
lastDarkTime = makeNonnegInt(millis());
} else {
lastDarkTime = makeEmptyInt();
}
}
}
/************************* Moisture sensing *********************************/
void moistureSetup() {
pinMode(moistResetPin, OUTPUT);
// reset the sensors to make them listen for I2C comms.
digitalWrite(moistResetPin, LOW);
delay(40);
digitalWrite(moistResetPin, HIGH);
delay(40);
// This is used to get moisture sensors into sensor mode instead of chirp mode
for (int i = 0; i < NUM_PLANTERS; i++) {
// This simple reset should work, but doesn't:
// writeI2CRegister8bit(moistures[i], 6);
// delay(500);
writeI2CRegister8bit(moistureSensorAddresses[i], 3);
// delay(200);
// readI2CRegister16bit(moistures[i], 0);
// readMoisture(i);
}
}
MoistureLevel readMoisture(int i) {
MoistureLevel ret;
ret.value = readI2CRegister16bit(moistureSensorAddresses[i], 0);
DEBUG_PRINT("Moisture level: ");
DEBUG_PRINT(ret.value);
return ret;
}
int moistureLow(MoistureLevel l) {
return l.value <= MOISTURE_THRESHOLD;
}
/************************ Pump control *************************************/
void pumpSetup(){
for (int i = 0; i < NUM_PLANTERS; i++) {
pinMode(pumpPins[i], OUTPUT);
}
}
void runPump(int i) {
DEBUG_PRINT("watering plant");
digitalWrite(pumpPins[i], HIGH);
delay(PUMP_TIME);
digitalWrite(pumpPins[i], LOW);
}
/************************ Water level *************************************/
int waterLow() {
int waterLevel = analogRead(waterLevelPin);
DEBUG_PRINT("Water level:");
DEBUG_PRINT(waterLevel);
return waterLevel < LOW_WATER_THRESHOLD;
}
/*********************** Main Arduino control *******************************/
void setup() {
Wire.begin();
Serial.begin(9600);
pumpSetup();
moistureSetup();
setupClock();
setupLights();
}
void loop() {
DEBUG_PRINT("tick");
#ifdef DEBUG
printCurTime();
#endif
pumpControl: {
for (int i = 0; i < NUM_PLANTERS; i++) {
DEBUG_PRINT("read sensor");
DEBUG_PRINT(readMoisture(i).value);
if (moistureLow(readMoisture(i))) {
runPump(i);
}
}
}
lightControl: {
updateDarkTime();
int lightShouldBeOn;
if (waterLow()) {
// Hijack the light mechanism to flash the lights
// to indicate low water
lightShouldBeOn = tickCount % 2 == 0;
} else {
lightShouldBeOn = !isEmpty(lastDarkTime);
}
DEBUG_PRINT("Dark time");
DEBUG_PRINT(lastDarkTime.value);
DEBUG_PRINT("Light should be on?");
DEBUG_PRINT(lightShouldBeOn);
trySetLights(lightShouldBeOn);
}
timeControl: {
tickCount++;
delay(TICK_TIME);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment