Created
December 11, 2019 06:31
-
-
Save jkoppel/df6415d8b508efeb0517ab2551bb93f6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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